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

Last change on this file since 513 was 513, checked in by gebner, 16 years ago

Implement a recursive-descent parser for SearchCompiler.

File size: 6.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.search;
3
4import java.io.IOException;
5import java.io.PushbackReader;
6import java.io.StringReader;
7import java.util.Map.Entry;
8import java.util.regex.Pattern;
9import java.util.regex.Matcher;
10
11import org.openstreetmap.josm.data.osm.Node;
12import org.openstreetmap.josm.data.osm.OsmPrimitive;
13import org.openstreetmap.josm.data.osm.Way;
14import org.openstreetmap.josm.data.osm.Relation;
15
16/**
17 * Implements a google-like search.
18 * @author Imi
19 */
20public class SearchCompiler {
21
22 private boolean caseSensitive = false;
23 private PushbackTokenizer tokenizer;
24
25 public SearchCompiler(boolean caseSensitive, PushbackTokenizer tokenizer) {
26 this.caseSensitive = caseSensitive;
27 this.tokenizer = tokenizer;
28 }
29
30 abstract public static class Match {
31 abstract public boolean match(OsmPrimitive osm);
32 }
33
34 private static class Always extends Match {
35 @Override public boolean match(OsmPrimitive osm) {
36 return true;
37 }
38 }
39
40 private static class Not extends Match {
41 private final Match match;
42 public Not(Match match) {this.match = match;}
43 @Override public boolean match(OsmPrimitive osm) {
44 return !match.match(osm);
45 }
46 @Override public String toString() {return "!"+match;}
47 }
48
49 private static class And extends Match {
50 private Match lhs;
51 private Match rhs;
52 public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
53 @Override public boolean match(OsmPrimitive osm) {
54 return lhs.match(osm) && rhs.match(osm);
55 }
56 @Override public String toString() {return lhs+" && "+rhs;}
57 }
58
59 private static class Or extends Match {
60 private Match lhs;
61 private Match rhs;
62 public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
63 @Override public boolean match(OsmPrimitive osm) {
64 return lhs.match(osm) || rhs.match(osm);
65 }
66 @Override public String toString() {return lhs+" || "+rhs;}
67 }
68
69 private static class Id extends Match {
70 private long id;
71 public Id(long id) {this.id = id;}
72 @Override public boolean match(OsmPrimitive osm) {
73 return osm.id == id;
74 }
75 @Override public String toString() {return "id="+id;}
76 }
77
78 private class KeyValue extends Match {
79 private String key;
80 private String value;
81 public KeyValue(String key, String value) {this.key = key; this.value = value; }
82 @Override public boolean match(OsmPrimitive osm) {
83 String value = null;
84 if (key.equals("timestamp"))
85 value = osm.getTimeStr();
86 else
87 value = osm.get(key);
88 if (value == null)
89 return false;
90 String v1 = caseSensitive ? value : value.toLowerCase();
91 String v2 = caseSensitive ? this.value : this.value.toLowerCase();
92 return v1.indexOf(v2) != -1;
93 }
94 @Override public String toString() {return key+"="+value;}
95 }
96
97 private class Any extends Match {
98 private String s;
99 public Any(String s) {this.s = s;}
100 @Override public boolean match(OsmPrimitive osm) {
101 if (osm.keys == null)
102 return s.equals("");
103 String search = caseSensitive ? s : s.toLowerCase();
104 for (Entry<String, String> e : osm.keys.entrySet()) {
105 String key = caseSensitive ? e.getKey() : e.getKey().toLowerCase();
106 String value = caseSensitive ? e.getValue() : e.getValue().toLowerCase();
107 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
108 return true;
109 }
110 if (osm.user != null) {
111 String name = osm.user.name;
112 if (!caseSensitive)
113 name = name.toLowerCase();
114 if (name.indexOf(search) != -1)
115 return true;
116 }
117 return false;
118 }
119 @Override public String toString() {return s;}
120 }
121
122 private static class ExactType extends Match {
123 private String type;
124 public ExactType(String type) {this.type = type;}
125 @Override public boolean match(OsmPrimitive osm) {
126 if (osm instanceof Node)
127 return type.equals("node");
128 if (osm instanceof Way)
129 return type.equals("way");
130 if (osm instanceof Relation)
131 return type.equals("relation");
132 throw new IllegalStateException("unknown class "+osm.getClass());
133 }
134 @Override public String toString() {return "type="+type;}
135 }
136
137 private static class Modified extends Match {
138 @Override public boolean match(OsmPrimitive osm) {
139 return osm.modified;
140 }
141 @Override public String toString() {return "modified";}
142 }
143
144 private static class Selected extends Match {
145 @Override public boolean match(OsmPrimitive osm) {
146 return osm.selected;
147 }
148 @Override public String toString() {return "selected";}
149 }
150
151 private static class Incomplete extends Match {
152 @Override public boolean match(OsmPrimitive osm) {
153 // return osm instanceof Way && ((Way)osm).isIncomplete();
154 return false;
155 }
156 @Override public String toString() {return "incomplete";}
157 }
158
159 public static Match compile(String searchStr, boolean caseSensitive) {
160 return new SearchCompiler(caseSensitive,
161 new PushbackTokenizer(
162 new PushbackReader(new StringReader(searchStr))))
163 .parse();
164 }
165
166 public Match parse() {
167 Match m = parseJuxta();
168 if (!tokenizer.readIfEqual(null)) {
169 throw new RuntimeException("Unexpected token: " + tokenizer.nextToken());
170 }
171 return m;
172 }
173
174 private Match parseJuxta() {
175 Match juxta = new Always();
176
177 Match m;
178 while ((m = parseOr()) != null) {
179 juxta = new And(m, juxta);
180 }
181
182 return juxta;
183 }
184
185 private Match parseOr() {
186 Match a = parseNot();
187 if (tokenizer.readIfEqual("|")) {
188 Match b = parseNot();
189 if (a == null || b == null) {
190 throw new RuntimeException("Missing arguments for or.");
191 }
192 return new Or(a, b);
193 }
194 return a;
195 }
196
197 private Match parseNot() {
198 if (tokenizer.readIfEqual("-")) {
199 Match m = parseParens();
200 if (m == null) {
201 throw new RuntimeException("Missing argument for not.");
202 }
203 return new Not(m);
204 }
205 return parseParens();
206 }
207
208 private Match parseParens() {
209 if (tokenizer.readIfEqual("(")) {
210 Match m = parseJuxta();
211 if (!tokenizer.readIfEqual(")")) {
212 throw new RuntimeException("Expected closing paren");
213 }
214 return m;
215 }
216 return parsePat();
217 }
218
219 private Match parsePat() {
220 String tok = tokenizer.readText();
221
222 if (tokenizer.readIfEqual(":")) {
223 String tok2 = tokenizer.readText();
224 if (tok == null) tok = "";
225 if (tok2 == null) tok2 = "";
226 return parseKV(tok, tok2);
227 }
228
229 if (tok == null) {
230 return null;
231 } else if (tok.equals("modified")) {
232 return new Modified();
233 } else if (tok.equals("incomplete")) {
234 return new Incomplete();
235 } else if (tok.equals("selected")) {
236 return new Selected();
237 } else {
238 return new Any(tok);
239 }
240 }
241
242 private Match parseKV(String key, String value) {
243 if (key.equals("type")) {
244 return new ExactType(value);
245 } else if (key.equals("id")) {
246 try {
247 return new Id(Long.parseLong(value));
248 } catch (NumberFormatException x) {
249 return new Id(0);
250 }
251 } else {
252 return new KeyValue(key, value);
253 }
254 }
255}
Note: See TracBrowser for help on using the repository browser.