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

Last change on this file since 6429 was 6429, checked in by simon04, 10 years ago

fix #8850 - search: support less/greater-than, e.g., start_date>1950

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