source: josm/trunk/src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java@ 12362

Last change on this file since 12362 was 12362, checked in by michael2402, 7 years ago

Javadoc: PushbackTokenizer

  • Property svn:eol-style set to native
File size: 9.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.search;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.io.IOException;
8import java.io.Reader;
9import java.util.Arrays;
10import java.util.List;
11import java.util.Objects;
12
13import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
14import org.openstreetmap.josm.tools.JosmRuntimeException;
15
16/**
17 * This class is used to parse a search string and split it into tokens.
18 * It provides methods to parse numbers and extract strings.
19 */
20public class PushbackTokenizer {
21
22 /**
23 * A range of long numbers. Immutable
24 */
25 public static class Range {
26 private final long start;
27 private final long end;
28
29 /**
30 * Create a new range
31 * @param start The start
32 * @param end The end (inclusive)
33 */
34 public Range(long start, long end) {
35 this.start = start;
36 this.end = end;
37 }
38
39 /**
40 * @return The start
41 */
42 public long getStart() {
43 return start;
44 }
45
46 /**
47 * @return The end (inclusive)
48 */
49 public long getEnd() {
50 return end;
51 }
52
53 @Override
54 public String toString() {
55 return "Range [start=" + start + ", end=" + end + ']';
56 }
57 }
58
59 private final Reader search;
60
61 private Token currentToken;
62 private String currentText;
63 private Long currentNumber;
64 private Long currentRange;
65 private int c;
66 private boolean isRange;
67
68 /**
69 * Creates a new {@link PushbackTokenizer}
70 * @param search The search string reader to read the tokens from
71 */
72 public PushbackTokenizer(Reader search) {
73 this.search = search;
74 getChar();
75 }
76
77 /**
78 * The token types that may be read
79 */
80 public enum Token {
81 /**
82 * Not token (-)
83 */
84 NOT(marktr("<not>")),
85 /**
86 * Or token (or) (|)
87 */
88 OR(marktr("<or>")),
89 /**
90 * Xor token (xor) (^)
91 */
92 XOR(marktr("<xor>")),
93 /**
94 * opening parentheses token (
95 */
96 LEFT_PARENT(marktr("<left parent>")),
97 /**
98 * closing parentheses token )
99 */
100 RIGHT_PARENT(marktr("<right parent>")),
101 /**
102 * Colon :
103 */
104 COLON(marktr("<colon>")),
105 /**
106 * The equals sign (=)
107 */
108 EQUALS(marktr("<equals>")),
109 /**
110 * A text
111 */
112 KEY(marktr("<key>")),
113 /**
114 * A question mark (?)
115 */
116 QUESTION_MARK(marktr("<question mark>")),
117 /**
118 * Marks the end of the input
119 */
120 EOF(marktr("<end-of-file>")),
121 /**
122 * Less than sign (<)
123 */
124 LESS_THAN("<less-than>"),
125 /**
126 * Greater than sign (>)
127 */
128 GREATER_THAN("<greater-than>");
129
130 Token(String name) {
131 this.name = name;
132 }
133
134 private final String name;
135
136 @Override
137 public String toString() {
138 return tr(name);
139 }
140 }
141
142 private void getChar() {
143 try {
144 c = search.read();
145 } catch (IOException e) {
146 throw new JosmRuntimeException(e.getMessage(), e);
147 }
148 }
149
150 private static final List<Character> specialChars = Arrays.asList('"', ':', '(', ')', '|', '^', '=', '?', '<', '>');
151 private static final List<Character> specialCharsQuoted = Arrays.asList('"');
152
153 private String getString(boolean quoted) {
154 List<Character> sChars = quoted ? specialCharsQuoted : specialChars;
155 StringBuilder s = new StringBuilder();
156 boolean escape = false;
157 while (c != -1 && (escape || (!sChars.contains((char) c) && (quoted || !Character.isWhitespace(c))))) {
158 if (c == '\\' && !escape) {
159 escape = true;
160 } else {
161 s.append((char) c);
162 escape = false;
163 }
164 getChar();
165 }
166 return s.toString();
167 }
168
169 private String getString() {
170 return getString(false);
171 }
172
173 /**
174 * The token returned is <code>null</code> or starts with an identifier character:
175 * - for an '-'. This will be the only character
176 * : for an key. The value is the next token
177 * | for "OR"
178 * ^ for "XOR"
179 * ' ' for anything else.
180 * @return The next token in the stream.
181 */
182 public Token nextToken() {
183 if (currentToken != null) {
184 Token result = currentToken;
185 currentToken = null;
186 return result;
187 }
188
189 while (Character.isWhitespace(c)) {
190 getChar();
191 }
192 switch (c) {
193 case -1:
194 getChar();
195 return Token.EOF;
196 case ':':
197 getChar();
198 return Token.COLON;
199 case '=':
200 getChar();
201 return Token.EQUALS;
202 case '<':
203 getChar();
204 return Token.LESS_THAN;
205 case '>':
206 getChar();
207 return Token.GREATER_THAN;
208 case '(':
209 getChar();
210 return Token.LEFT_PARENT;
211 case ')':
212 getChar();
213 return Token.RIGHT_PARENT;
214 case '|':
215 getChar();
216 return Token.OR;
217 case '^':
218 getChar();
219 return Token.XOR;
220 case '&':
221 getChar();
222 return nextToken();
223 case '?':
224 getChar();
225 return Token.QUESTION_MARK;
226 case '"':
227 getChar();
228 currentText = getString(true);
229 getChar();
230 return Token.KEY;
231 default:
232 String prefix = "";
233 if (c == '-') {
234 getChar();
235 if (!Character.isDigit(c))
236 return Token.NOT;
237 prefix = "-";
238 }
239 currentText = prefix + getString();
240 if ("or".equalsIgnoreCase(currentText))
241 return Token.OR;
242 else if ("xor".equalsIgnoreCase(currentText))
243 return Token.XOR;
244 else if ("and".equalsIgnoreCase(currentText))
245 return nextToken();
246 // try parsing number
247 try {
248 currentNumber = Long.valueOf(currentText);
249 } catch (NumberFormatException e) {
250 currentNumber = null;
251 }
252 // if text contains "-", try parsing a range
253 int pos = currentText.indexOf('-', 1);
254 isRange = pos > 0;
255 if (isRange) {
256 try {
257 currentNumber = Long.valueOf(currentText.substring(0, pos));
258 } catch (NumberFormatException e) {
259 currentNumber = null;
260 }
261 try {
262 currentRange = Long.valueOf(currentText.substring(pos + 1));
263 } catch (NumberFormatException e) {
264 currentRange = null;
265 }
266 } else {
267 currentRange = null;
268 }
269 return Token.KEY;
270 }
271 }
272
273 /**
274 * Reads the next token if it is equal to the given, suggested token
275 * @param token The token the next one should be equal to
276 * @return <code>true</code> if it has been read
277 */
278 public boolean readIfEqual(Token token) {
279 Token nextTok = nextToken();
280 if (Objects.equals(nextTok, token))
281 return true;
282 currentToken = nextTok;
283 return false;
284 }
285
286 /**
287 * Reads the next token. If it is a text, return that text. If not, advance
288 * @return the text or <code>null</code> if the reader was advanced
289 */
290 public String readTextOrNumber() {
291 Token nextTok = nextToken();
292 if (nextTok == Token.KEY)
293 return currentText;
294 currentToken = nextTok;
295 return null;
296 }
297
298 /**
299 * Reads a number
300 * @param errorMessage The error if the number cannot be read
301 * @return The number that was found
302 * @throws ParseError if there is no number
303 */
304 public long readNumber(String errorMessage) throws ParseError {
305 if ((nextToken() == Token.KEY) && (currentNumber != null))
306 return currentNumber;
307 else
308 throw new ParseError(errorMessage);
309 }
310
311 /**
312 * Gets the last number that was read
313 * @return The last number
314 */
315 public long getReadNumber() {
316 return (currentNumber != null) ? currentNumber : 0;
317 }
318
319 /**
320 * Reads a range of numbers
321 * @param errorMessage The error if the input is malformed
322 * @return The range that was found
323 * @throws ParseError If the input is not as expected for a range
324 */
325 public Range readRange(String errorMessage) throws ParseError {
326 if (nextToken() != Token.KEY || (currentNumber == null && currentRange == null)) {
327 throw new ParseError(errorMessage);
328 } else if (!isRange && currentNumber != null) {
329 if (currentNumber >= 0) {
330 return new Range(currentNumber, currentNumber);
331 } else {
332 return new Range(0, Math.abs(currentNumber));
333 }
334 } else if (isRange && currentRange == null) {
335 return new Range(currentNumber, Integer.MAX_VALUE);
336 } else if (currentNumber != null && currentRange != null) {
337 return new Range(currentNumber, currentRange);
338 } else {
339 throw new ParseError(errorMessage);
340 }
341 }
342
343 /**
344 * Gets the last text that was found
345 * @return The text
346 */
347 public String getText() {
348 return currentText;
349 }
350}
Note: See TracBrowser for help on using the repository browser.