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

Last change on this file since 8377 was 7463, checked in by Don-vip, 10 years ago

fix #10392 - rework of MenuScroller to replace static scrollCount approach by dynamic behaviour (fix regressions from #10207)

  • Property svn:eol-style set to native
File size: 7.4 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;
14
15public class PushbackTokenizer {
16
17 public static class Range {
18 private final long start;
19 private final long end;
20
21 public Range(long start, long end) {
22 this.start = start;
23 this.end = end;
24 }
25
26 public long getStart() {
27 return start;
28 }
29
30 public long getEnd() {
31 return end;
32 }
33
34 @Override
35 public String toString() {
36 return "Range [start=" + start + ", end=" + end + "]";
37 }
38 }
39
40 private final Reader search;
41
42 private Token currentToken;
43 private String currentText;
44 private Long currentNumber;
45 private Long currentRange;
46 private int c;
47 private boolean isRange;
48
49 public PushbackTokenizer(Reader search) {
50 this.search = search;
51 getChar();
52 }
53
54 public enum Token {
55 NOT(marktr("<not>")), OR(marktr("<or>")), XOR(marktr("<xor>")), LEFT_PARENT(marktr("<left parent>")),
56 RIGHT_PARENT(marktr("<right parent>")), COLON(marktr("<colon>")), EQUALS(marktr("<equals>")),
57 KEY(marktr("<key>")), QUESTION_MARK(marktr("<question mark>")),
58 EOF(marktr("<end-of-file>")), LESS_THAN("<less-than>"), GREATER_THAN("<greater-than>");
59
60 private Token(String name) {
61 this.name = name;
62 }
63
64 private final String name;
65
66 @Override
67 public String toString() {
68 return tr(name);
69 }
70 }
71
72
73 private void getChar() {
74 try {
75 c = search.read();
76 } catch (IOException e) {
77 throw new RuntimeException(e.getMessage(), e);
78 }
79 }
80
81 private static final List<Character> specialChars = Arrays.asList('"', ':', '(', ')', '|', '^', '=', '?', '<', '>');
82 private static final List<Character> specialCharsQuoted = Arrays.asList('"');
83
84 private String getString(boolean quoted) {
85 List<Character> sChars = quoted ? specialCharsQuoted : specialChars;
86 StringBuilder s = new StringBuilder();
87 boolean escape = false;
88 while (c != -1 && (escape || (!sChars.contains((char)c) && (quoted || !Character.isWhitespace(c))))) {
89 if (c == '\\' && !escape) {
90 escape = true;
91 } else {
92 s.append((char)c);
93 escape = false;
94 }
95 getChar();
96 }
97 return s.toString();
98 }
99
100 private String getString() {
101 return getString(false);
102 }
103
104 /**
105 * The token returned is <code>null</code> or starts with an identifier character:
106 * - for an '-'. This will be the only character
107 * : for an key. The value is the next token
108 * | for "OR"
109 * ^ for "XOR"
110 * ' ' for anything else.
111 * @return The next token in the stream.
112 */
113 public Token nextToken() {
114 if (currentToken != null) {
115 Token result = currentToken;
116 currentToken = null;
117 return result;
118 }
119
120 while (Character.isWhitespace(c)) {
121 getChar();
122 }
123 switch (c) {
124 case -1:
125 getChar();
126 return Token.EOF;
127 case ':':
128 getChar();
129 return Token.COLON;
130 case '=':
131 getChar();
132 return Token.EQUALS;
133 case '<':
134 getChar();
135 return Token.LESS_THAN;
136 case '>':
137 getChar();
138 return Token.GREATER_THAN;
139 case '(':
140 getChar();
141 return Token.LEFT_PARENT;
142 case ')':
143 getChar();
144 return Token.RIGHT_PARENT;
145 case '|':
146 getChar();
147 return Token.OR;
148 case '^':
149 getChar();
150 return Token.XOR;
151 case '&':
152 getChar();
153 return nextToken();
154 case '?':
155 getChar();
156 return Token.QUESTION_MARK;
157 case '"':
158 getChar();
159 currentText = getString(true);
160 getChar();
161 return Token.KEY;
162 default:
163 String prefix = "";
164 if (c == '-') {
165 getChar();
166 if (!Character.isDigit(c))
167 return Token.NOT;
168 prefix = "-";
169 }
170 currentText = prefix + getString();
171 if ("or".equalsIgnoreCase(currentText))
172 return Token.OR;
173 else if ("xor".equalsIgnoreCase(currentText))
174 return Token.XOR;
175 else if ("and".equalsIgnoreCase(currentText))
176 return nextToken();
177 // try parsing number
178 try {
179 currentNumber = Long.parseLong(currentText);
180 } catch (NumberFormatException e) {
181 currentNumber = null;
182 }
183 // if text contains "-", try parsing a range
184 int pos = currentText.indexOf('-', 1);
185 isRange = pos > 0;
186 if (isRange) {
187 try {
188 currentNumber = Long.parseLong(currentText.substring(0, pos));
189 } catch (NumberFormatException e) {
190 currentNumber = null;
191 }
192 try {
193 currentRange = Long.parseLong(currentText.substring(pos + 1));
194 } catch (NumberFormatException e) {
195 currentRange = null;
196 }
197 } else {
198 currentRange = null;
199 }
200 return Token.KEY;
201 }
202 }
203
204 public boolean readIfEqual(Token token) {
205 Token nextTok = nextToken();
206 if (Objects.equals(nextTok, token))
207 return true;
208 currentToken = nextTok;
209 return false;
210 }
211
212 public String readTextOrNumber() {
213 Token nextTok = nextToken();
214 if (nextTok == Token.KEY)
215 return currentText;
216 currentToken = nextTok;
217 return null;
218 }
219
220 public long readNumber(String errorMessage) throws ParseError {
221 if ((nextToken() == Token.KEY) && (currentNumber != null))
222 return currentNumber;
223 else
224 throw new ParseError(errorMessage);
225 }
226
227 public long getReadNumber() {
228 return (currentNumber != null) ? currentNumber : 0;
229 }
230
231 public Range readRange(String errorMessage) throws ParseError {
232 if (nextToken() != Token.KEY || (currentNumber == null && currentRange == null)) {
233 throw new ParseError(errorMessage);
234 } else if (!isRange && currentNumber != null) {
235 if (currentNumber >= 0) {
236 return new Range(currentNumber, currentNumber);
237 } else {
238 return new Range(0, Math.abs(currentNumber));
239 }
240 } else if (isRange && currentRange == null) {
241 return new Range(currentNumber, Integer.MAX_VALUE);
242 } else if (currentNumber != null && currentRange != null) {
243 return new Range(currentNumber, currentRange);
244 } else {
245 throw new ParseError(errorMessage);
246 }
247 }
248
249 public String getText() {
250 return currentText;
251 }
252}
Note: See TracBrowser for help on using the repository browser.