Gridarta Editor
RegExParser.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.var.crossfire.model.validation.checks;
21 
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28 import org.jetbrains.annotations.NotNull;
29 
33 public class RegExParser {
34 
38  @NotNull
39  private static final Pattern PATTERN_UPPER_CASE_LETTER = Pattern.compile("[A-Z]");
40 
44  @NotNull
45  private final Collection<String> words = new ArrayList<>();
46 
52  @SuppressWarnings("CharacterComparison")
53  public void parse(@NotNull final String regEx, @NotNull final ErrorGenerator<?, ?, ?> generator) {
54  words.clear();
55 
56  for (final char ch : regEx.toCharArray()) {
57  if (ch < 32 || ch >= 128) {
58  generator.errorRegEx("non-ASCII character"); // works in Crossfire but probably will not work reliably in all clients
59  }
60  }
61 
62  int pos = 0;
63  int wordIndex = 0;
64  final boolean[] range = new boolean[128];
65  while (pos < regEx.length()) {
66  final char ch = regEx.charAt(pos++);
67  switch (ch) {
68  case '?':
69  generator.errorRegEx("use of regular expression operator '?'. This is usually not useful and therefore should be avoided");
70  break;
71 
72  case '*':
73  generator.errorRegEx("use of regular expression operator '*'. This is usually not useful and therefore should be avoided");
74  break;
75 
76  case '+':
77  generator.errorRegEx("use of regular expression operator '+'. This is usually not useful and therefore should be avoided");
78  break;
79 
80  case '.':
81  generator.errorRegEx("use of regular expression operator '.'. This is usually not useful and therefore should be avoided");
82  break;
83 
84  case '[':
85  if (pos < regEx.length() && regEx.charAt(pos) == '^') {
86  generator.errorRegEx("negated character range"); // works in Crossfire but is not useful
87  }
88  Arrays.fill(range, false);
89  final int beginIndex = pos - 1;
90  boolean reportError = true;
91  while (pos < regEx.length() && regEx.charAt(pos) != ']') {
92  final char begin = regEx.charAt(pos++);
93  if (pos < regEx.length() && regEx.charAt(pos) == '-') {
94  pos++;
95  if (pos >= regEx.length()) {
96  break;
97  }
98  final char end = regEx.charAt(pos++);
99  if (end == ']') {
100  generator.errorRegEx("confusing trailing '-' in character range"); // works in Crossfire but is very confusing
101  reportError = false;
102  pos--;
103  break;
104  }
105  final boolean ok;
106  if ('a' <= begin && begin <= 'z' && 'a' <= end && end <= 'z') {
107  ok = true;
108  } else if ('A' <= begin && begin <= 'Z' && 'A' <= end && end <= 'Z') {
109  ok = true;
110  } else if ('0' <= begin && begin <= '9' && '0' <= end && end <= '9') {
111  ok = true;
112  } else {
113  generator.errorRegEx("character ranges should use only a-z, A-Z, or 0-9");
114  reportError = false;
115  ok = false;
116  }
117  if (ok) {
118  if (begin > end) {
119  generator.errorRegEx("invalid character range");
120  reportError = false;
121  } else if (begin == end) {
122  generator.errorRegEx("single-character character range"); // works in Crossfire but is useless
123  reportError = false;
124  }
125  for (char tmp = begin; tmp <= end; tmp++) {
126  if (range[tmp]) {
127  generator.errorRegEx("duplicate character '" + tmp + "' in character range");
128  reportError = false;
129  break;
130  }
131  range[tmp] = true;
132  }
133  }
134  } else {
135  if (range[begin]) {
136  generator.errorRegEx("duplicate character '" + begin + "' in character range");
137  reportError = false;
138  }
139  range[begin] = true;
140  }
141  }
142  if (pos >= regEx.length()) {
143  generator.errorRegEx("unterminated character range");
144  break;
145  }
146  pos++;
147  if (reportError) {
148  int i;
149  for (i = 32; i < range.length; i++) {
150  if (range[i]) {
151  break;
152  }
153  }
154  if (i >= 128) {
155  generator.errorRegEx("empty character range"); // works in Crossfire but is useless
156  } else {
157  int j;
158  for (j = i + 1; j < range.length; j++) {
159  if (range[j]) {
160  break;
161  }
162  }
163  if (j >= 128 && i != '?' && i != '*' && i != '+' && i != '.' && i != '[' && i != '\\' && i != '^' && i != '$' && i != '|') {
164  generator.errorRegEx("the word '" + regEx + "' is the same as '" + regEx.substring(0, beginIndex) + (char) i + regEx.substring(pos) + "' which is probably not what was intended"); // useless
165  }
166  }
167  }
168  break;
169 
170  case '\\':
171  //noinspection SimplifiableIfStatement
172  if (pos >= regEx.length()) {
173  generator.errorRegEx("trailing \\");
174  } else {
175  generator.errorRegEx("use of regular expression operator '\\'. This is usually not useful and therefore should be avoided");
176  }
177  pos++;
178  break;
179 
180  case '^':
181  if (pos != 1) {
182  generator.errorRegEx("the regular expression operator '^' must be used at the start of the word");
183  }
184  break;
185 
186  case '$':
187  if (pos != regEx.length()) {
188  generator.errorRegEx("the regular expression operator '$' must be used at the end of the word");
189  }
190  break;
191 
192  case '|':
193  addWord(regEx.substring(wordIndex, pos - 1), generator);
194  wordIndex = pos;
195  break;
196  }
197  }
198 
199  addWord(regEx.substring(wordIndex), generator);
200  }
201 
207  private void addWord(@NotNull final String word, @NotNull final ErrorGenerator<?, ?, ?> generator) {
208  words.add(word);
209 
210  if (word.startsWith(" ")) {
211  generator.errorRegEx("the word '" + word + "' starts with a space");
212  } else if (word.endsWith(" ")) {
213  generator.errorRegEx("the word '" + word + "' ends with a space");
214  }
215  final Matcher matcher = PATTERN_UPPER_CASE_LETTER.matcher(word);
216  if (matcher.find()) {
217  generator.errorRegEx("the word '" + word + "' checks for upper-case letters. Matches are case-insensitive, therefore matches should be specified in lower-case letters only");
218  }
219  }
220 
226  @NotNull
227  public Collection<String> getWords() {
228  return Collections.unmodifiableCollection(words);
229  }
230 
231 }
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser.PATTERN_UPPER_CASE_LETTER
static final Pattern PATTERN_UPPER_CASE_LETTER
Matches any upper-case ASCI letter.
Definition: RegExParser.java:39
net.sf.gridarta.var.crossfire.model.validation.checks.ErrorGenerator
Generator for SuspiciousMsgChecker related error messages.
Definition: ErrorGenerator.java:38
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser.words
final Collection< String > words
The '|' separated words.
Definition: RegExParser.java:45
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser.addWord
void addWord(@NotNull final String word, @NotNull final ErrorGenerator<?, ?, ?> generator)
Adds a word to words.
Definition: RegExParser.java:207
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser
Parser for Crossfire regular expressions.
Definition: RegExParser.java:33
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser.getWords
Collection< String > getWords()
Returns the words from the last call to ErrorGenerator).
Definition: RegExParser.java:227
net.sf.gridarta.var.crossfire.model.validation.checks.RegExParser.parse
void parse(@NotNull final String regEx, @NotNull final ErrorGenerator<?, ?, ?> generator)
Parses a regular expression.
Definition: RegExParser.java:53