source: josm/src/org/openstreetmap/josm/actions/search/SearchCompiler.java@ 207

Last change on this file since 207 was 207, checked in by imi, 17 years ago
  • added "case sensitive" option to search dialog
  • added a plugin manifest option "Plugin-Stage" to specify boot stage order (default is 50, smaller first)
File size: 8.2 KB
Line 
1package org.openstreetmap.josm.actions.search;
2
3import java.io.IOException;
4import java.io.PushbackReader;
5import java.io.StringReader;
6import java.util.Map.Entry;
7
8import org.openstreetmap.josm.data.osm.Node;
9import org.openstreetmap.josm.data.osm.OsmPrimitive;
10import org.openstreetmap.josm.data.osm.Segment;
11import org.openstreetmap.josm.data.osm.Way;
12
13/**
14 * Implements a google-like search.
15 * @author Imi
16 */
17public class SearchCompiler {
18
19 boolean caseSensitive = false;
20
21 abstract public static class Match {
22 abstract public boolean match(OsmPrimitive osm);
23 }
24
25 private static class Always extends Match {
26 @Override public boolean match(OsmPrimitive osm) {
27 return true;
28 }
29 }
30
31 private static class Not extends Match {
32 private final Match match;
33 public Not(Match match) {this.match = match;}
34 @Override public boolean match(OsmPrimitive osm) {
35 return !match.match(osm);
36 }
37 @Override public String toString() {return "!"+match;}
38 }
39
40 private static class And extends Match {
41 private Match lhs;
42 private Match rhs;
43 public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
44 @Override public boolean match(OsmPrimitive osm) {
45 return lhs.match(osm) && rhs.match(osm);
46 }
47 @Override public String toString() {return lhs+" && "+rhs;}
48 }
49
50 private static class Or extends Match {
51 private Match lhs;
52 private Match rhs;
53 public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
54 @Override public boolean match(OsmPrimitive osm) {
55 return lhs.match(osm) || rhs.match(osm);
56 }
57 @Override public String toString() {return lhs+" || "+rhs;}
58 }
59
60 private static class Id extends Match {
61 private long id;
62 public Id(long id) {this.id = id;}
63 @Override public boolean match(OsmPrimitive osm) {
64 return osm.id == id;
65 }
66 @Override public String toString() {return "id="+id;}
67 }
68
69 private class KeyValue extends Match {
70 private String key;
71 private String value;
72 boolean notValue;
73 public KeyValue(String key, String value, boolean notValue) {this.key = key; this.value = value; this.notValue = notValue;}
74 @Override public boolean match(OsmPrimitive osm) {
75 String value = null;
76 if (key.equals("timestamp"))
77 value = osm.getTimeStr();
78 else
79 value = osm.get(key);
80 if (value == null)
81 return notValue;
82 String v1 = caseSensitive ? value : value.toLowerCase();
83 String v2 = caseSensitive ? this.value : this.value.toLowerCase();
84 return (v1.indexOf(v2) != -1) != notValue;
85 }
86 @Override public String toString() {return key+"="+(notValue?"!":"")+value;}
87 }
88
89 private class Any extends Match {
90 private String s;
91 public Any(String s) {this.s = s;}
92 @Override public boolean match(OsmPrimitive osm) {
93 if (osm.keys == null)
94 return s.equals("");
95 for (Entry<String, String> e : osm.keys.entrySet()) {
96 String key = caseSensitive ? e.getKey() : e.getKey().toLowerCase();
97 String value = caseSensitive ? e.getValue() : e.getValue().toLowerCase();
98 String search = caseSensitive ? s : s.toLowerCase();
99 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
100 return true;
101 }
102 return false;
103 }
104 @Override public String toString() {return s;}
105 }
106
107 private static class ExactType extends Match {
108 private String type;
109 public ExactType(String type) {this.type = type;}
110 @Override public boolean match(OsmPrimitive osm) {
111 if (osm instanceof Node)
112 return type.equals("node");
113 if (osm instanceof Segment)
114 return type.equals("segment");
115 if (osm instanceof Way)
116 return type.equals("way");
117 throw new IllegalStateException("unknown class "+osm.getClass());
118 }
119 @Override public String toString() {return "type="+type;}
120 }
121
122 private static class Modified extends Match {
123 @Override public boolean match(OsmPrimitive osm) {
124 return osm.modified;
125 }
126 @Override public String toString() {return "modified";}
127 }
128
129 private static class Selected extends Match {
130 @Override public boolean match(OsmPrimitive osm) {
131 return osm.selected;
132 }
133 @Override public String toString() {return "selected";}
134 }
135
136 private static class Incomplete extends Match {
137 @Override public boolean match(OsmPrimitive osm) {
138 return osm instanceof Way && ((Way)osm).isIncomplete();
139 }
140 @Override public String toString() {return "incomplete";}
141 }
142
143 public static Match compile(String searchStr, boolean caseSensitive) {
144 SearchCompiler searchCompiler = new SearchCompiler();
145 searchCompiler.caseSensitive = caseSensitive;
146 return searchCompiler.parse(new PushbackReader(new StringReader(searchStr)));
147 }
148
149
150 /**
151 * The token returned is <code>null</code> or starts with an identifier character:
152 * - for an '-'. This will be the only character
153 * : for an key. The value is the next token
154 * | for "OR"
155 * ' ' for anything else.
156 * @return The next token in the stream.
157 */
158 private String nextToken(PushbackReader search) {
159 try {
160 int next;
161 char c = ' ';
162 while (c == ' ' || c == '\t' || c == '\n') {
163 next = search.read();
164 if (next == -1)
165 return null;
166 c = (char)next;
167 }
168 StringBuilder s;
169 switch (c) {
170 case '-':
171 return "-";
172 case '"':
173 s = new StringBuilder(" ");
174 for (int nc = search.read(); nc != -1 && nc != '"'; nc = search.read())
175 s.append((char)nc);
176 int nc = search.read();
177 if (nc != -1 && (char)nc == ':')
178 return ":"+s.toString();
179 if (nc != -1)
180 search.unread(nc);
181 return s.toString();
182 default:
183 s = new StringBuilder();
184 for (;;) {
185 s.append(c);
186 next = search.read();
187 if (next == -1) {
188 if (s.toString().equals("OR"))
189 return "|";
190 return " "+s.toString();
191 }
192 c = (char)next;
193 if (c == ' ' || c == '\t' || c == ':' || c == '"') {
194 if (c == ':')
195 return ":"+s.toString();
196 search.unread(next);
197 if (s.toString().equals("OR"))
198 return "|";
199 return " "+s.toString();
200 }
201 }
202 }
203 } catch (IOException e) {
204 throw new RuntimeException(e.getMessage(), e);
205 }
206 }
207
208
209 private boolean notKey = false;
210 private boolean notValue = false;
211 private boolean or = false;
212 private String key = null;
213 String token = null;
214 private Match build() {
215 String value = token.substring(1);
216 if (key == null) {
217 Match c = null;
218 if (value.equals("modified"))
219 c = new Modified();
220 else if (value.equals("incomplete"))
221 c = new Incomplete();
222 else if (value.equals("selected"))
223 c = new Selected();
224 else
225 c = new Any(value);
226 return notValue ? new Not(c) : c;
227 }
228 Match c;
229 if (key.equals("type"))
230 c = new ExactType(value);
231 else if (key.equals("property")) {
232 String realKey = "", realValue = value;
233 int eqPos = value.indexOf("=");
234 if (eqPos != -1) {
235 realKey = value.substring(0,eqPos);
236 realValue = value.substring(eqPos+1);
237 }
238 c = new KeyValue(realKey, realValue, notValue);
239 } else if (key.equals("id")) {
240 try {
241 c = new Id(Long.parseLong(value));
242 } catch (NumberFormatException x) {
243 c = new Id(0);
244 }
245 if (notValue)
246 c = new Not(c);
247 } else
248 c = new KeyValue(key, value, notValue);
249 if (notKey)
250 return new Not(c);
251 return c;
252 }
253
254 private Match parse(PushbackReader search) {
255 Match result = null;
256 for (token = nextToken(search); token != null; token = nextToken(search)) {
257 if (token.equals("-"))
258 notValue = true;
259 else if (token.equals("|")) {
260 if (result == null)
261 continue;
262 or = true;
263 notValue = false;
264 } else if (token.startsWith(":")) {
265 if (key == null) {
266 key = token.substring(1);
267 notKey = notValue;
268 notValue = false;
269 } else
270 key += token.substring(1);
271 } else {
272 Match current = build();
273 if (result == null)
274 result = current;
275 else
276 result = or ? new Or(result, current) : new And(result, current);
277 key = null;
278 notKey = false;
279 notValue = false;
280 or = false;
281 }
282 }
283 // if "key:" was the last search
284 if (key != null) {
285 token = " ";
286 Match current = build();
287 result = (result == null) ? current : new And(result, current);
288 }
289 return result == null ? new Always() : result;
290 }
291}
Note: See TracBrowser for help on using the repository browser.