root / branches / Mobile_Compatible_Hito_1 / libFMap_data / src / org / gvsig / data / vectorial / filter / LikeFilterImpl.java @ 21563
History | View | Annotate | Download (12.6 KB)
1 |
/*
|
---|---|
2 |
* GeoTools - OpenSource mapping toolkit
|
3 |
* http://geotools.org
|
4 |
* (C) 2002-2006, GeoTools Project Managment Committee (PMC)
|
5 |
*
|
6 |
* This library is free software; you can redistribute it and/or
|
7 |
* modify it under the terms of the GNU Lesser General Public
|
8 |
* License as published by the Free Software Foundation;
|
9 |
* version 2.1 of the License.
|
10 |
*
|
11 |
* This library is distributed in the hope that it will be useful,
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
* Lesser General Public License for more details.
|
15 |
*/
|
16 |
package org.gvsig.data.vectorial.filter; |
17 |
|
18 |
|
19 |
import java.util.regex.Matcher; |
20 |
import java.util.regex.Pattern; |
21 |
|
22 |
import org.opengis.filter.FilterVisitor; |
23 |
import org.opengis.filter.PropertyIsLike; |
24 |
import org.opengis.filter.expression.Expression; |
25 |
|
26 |
|
27 |
/**
|
28 |
* Defines a like filter, which checks to see if an attribute matches a REGEXP.
|
29 |
*
|
30 |
* @author Rob Hranac, Vision for New York
|
31 |
* @source $URL: http://svn.geotools.org/geotools/tags/2.4.2/modules/library/main/src/main/java/org/geotools/filter/LikeFilterImpl.java $
|
32 |
* @version $Id: LikeFilterImpl.java 24805 2007-03-19 15:15:35Z aaime $
|
33 |
*/
|
34 |
public class LikeFilterImpl implements PropertyIsLike { |
35 |
|
36 |
/** The attribute value, which must be an attribute expression. */
|
37 |
private Expression attribute = null; |
38 |
|
39 |
/** The (limited) REGEXP pattern. */
|
40 |
private String pattern = null; |
41 |
|
42 |
/** The single wildcard for the REGEXP pattern. */
|
43 |
private String wildcardSingle = ".?"; |
44 |
|
45 |
/** The multiple wildcard for the REGEXP pattern. */
|
46 |
private String wildcardMulti = ".*"; |
47 |
|
48 |
/** The escape sequence for the REGEXP pattern. */
|
49 |
private String escape = "\\"; |
50 |
|
51 |
/** the pattern compiled into a java regex */
|
52 |
private Pattern compPattern = null; |
53 |
|
54 |
/** The matcher to match patterns with. */
|
55 |
private Matcher match = null; |
56 |
|
57 |
protected LikeFilterImpl(Expression expression, String pattern){ |
58 |
this(expression, pattern,"%","_","\\"); |
59 |
|
60 |
} |
61 |
|
62 |
protected LikeFilterImpl(Expression expression, String pattern, String wildchar, String singlechar, String escape){ |
63 |
attribute = expression; |
64 |
this.pattern=pattern;
|
65 |
setSingleChar(singlechar); |
66 |
setWildCard(wildchar); |
67 |
setEscape(escape); |
68 |
} |
69 |
|
70 |
/**
|
71 |
* Given OGC PropertyIsLike Filter information, construct
|
72 |
* an SQL-compatible 'like' pattern.
|
73 |
*
|
74 |
* SQL % --> match any number of characters
|
75 |
* _ --> match a single character
|
76 |
*
|
77 |
* NOTE; the SQL command is 'string LIKE pattern [ESCAPE escape-character]'
|
78 |
* We could re-define the escape character, but I'm not doing to do that in this code
|
79 |
* since some databases will not handle this case.
|
80 |
*
|
81 |
* Method:
|
82 |
* 1.
|
83 |
*
|
84 |
* Examples: ( escape ='!', multi='*', single='.' )
|
85 |
* broadway* -> 'broadway%'
|
86 |
* broad_ay -> 'broad_ay'
|
87 |
* broadway -> 'broadway'
|
88 |
*
|
89 |
* broadway!* -> 'broadway*' (* has no significance and is escaped)
|
90 |
* can't -> 'can''t' ( ' escaped for SQL compliance)
|
91 |
*
|
92 |
*
|
93 |
* NOTE: we also handle "'" characters as special because they are
|
94 |
* end-of-string characters. SQL will convert ' to '' (double single quote).
|
95 |
*
|
96 |
* NOTE: we dont handle "'" as a 'special' character because it would be
|
97 |
* too confusing to have a special char as another special char.
|
98 |
* Using this will throw an error (IllegalArgumentException).
|
99 |
*
|
100 |
* @param escape
|
101 |
* @param multi
|
102 |
* @param single
|
103 |
* @param pattern
|
104 |
*
|
105 |
*/
|
106 |
|
107 |
|
108 |
public void setWildCard(String wildCard) { |
109 |
this.wildcardMulti = wildCard;
|
110 |
match = null;
|
111 |
} |
112 |
|
113 |
public void setSingleChar(String singleChar) { |
114 |
this.wildcardSingle = singleChar;
|
115 |
match = null;
|
116 |
} |
117 |
|
118 |
public void setEscape(String escape) { |
119 |
this.escape = escape;
|
120 |
match = null;
|
121 |
} |
122 |
|
123 |
|
124 |
private Matcher getMatcher(){ |
125 |
if(match == null){ |
126 |
// protect the vars as this is moved code
|
127 |
|
128 |
String pattern1 = new String(this.pattern); |
129 |
String wildcardMulti1 = new String(this.wildcardMulti); |
130 |
String wildcardSingle1 = new String(this.wildcardSingle); |
131 |
String escape1 = new String(this.escape); |
132 |
|
133 |
// The following things happen for both wildcards:
|
134 |
// (1) If a user-defined wildcard exists, replace with Java wildcard
|
135 |
// (2) If a user-defined escape exists, Java wildcard + user-escape
|
136 |
// Then, test for matching pattern and return result.
|
137 |
char esc = escape1.charAt(0); |
138 |
|
139 |
String escapedWildcardMulti = fixSpecials(wildcardMulti1);
|
140 |
String escapedWildcardSingle = fixSpecials(wildcardSingle1);
|
141 |
|
142 |
// escape any special chars which are not our wildcards
|
143 |
StringBuffer tmp = new StringBuffer(""); |
144 |
|
145 |
boolean escapedMode = false; |
146 |
|
147 |
for (int i = 0; i < pattern1.length(); i++) { |
148 |
char chr = pattern1.charAt(i);
|
149 |
//LOGGER.finer("tmp = " + tmp + " looking at " + chr);
|
150 |
|
151 |
if (pattern1.regionMatches(false, i, escape1, 0, escape1.length())) { |
152 |
// skip the escape string
|
153 |
//LOGGER.finer("escape ");
|
154 |
escapedMode = true;
|
155 |
|
156 |
i += escape1.length(); |
157 |
chr = pattern1.charAt(i); |
158 |
} |
159 |
|
160 |
if (pattern1.regionMatches(false, i, wildcardMulti1, 0, |
161 |
wildcardMulti1.length())) { // replace with java wildcard
|
162 |
//LOGGER.finer("multi wildcard");
|
163 |
|
164 |
if (escapedMode) {
|
165 |
//LOGGER.finer("escaped ");
|
166 |
tmp.append(escapedWildcardMulti); |
167 |
} else {
|
168 |
tmp.append(".*");
|
169 |
} |
170 |
|
171 |
i += (wildcardMulti1.length() - 1);
|
172 |
escapedMode = false;
|
173 |
|
174 |
continue;
|
175 |
} |
176 |
|
177 |
if (pattern1.regionMatches(false, i, wildcardSingle1, 0, |
178 |
wildcardSingle1.length())) { |
179 |
// replace with java single wild card
|
180 |
//LOGGER.finer("single wildcard");
|
181 |
|
182 |
if (escapedMode) {
|
183 |
//LOGGER.finer("escaped ");
|
184 |
tmp.append(escapedWildcardSingle); |
185 |
} else {
|
186 |
// From the OpenGIS filter encoding spec,
|
187 |
// "the single singleChar character matches exactly one character"
|
188 |
tmp.append(".{1}");
|
189 |
} |
190 |
|
191 |
i += (wildcardSingle1.length() - 1);
|
192 |
escapedMode = false;
|
193 |
|
194 |
continue;
|
195 |
} |
196 |
|
197 |
if (isSpecial(chr)) {
|
198 |
//LOGGER.finer("special");
|
199 |
tmp.append(this.escape + chr);
|
200 |
escapedMode = false;
|
201 |
|
202 |
continue;
|
203 |
} |
204 |
|
205 |
tmp.append(chr); |
206 |
escapedMode = false;
|
207 |
} |
208 |
|
209 |
pattern1 = tmp.toString(); |
210 |
//LOGGER.finer("final pattern " + pattern1);
|
211 |
compPattern = java.util.regex.Pattern.compile(pattern1); |
212 |
match = compPattern.matcher("");
|
213 |
} |
214 |
return match;
|
215 |
} |
216 |
|
217 |
/**
|
218 |
* Returns the pattern.
|
219 |
*/
|
220 |
public String getLiteral() { |
221 |
return this.pattern; |
222 |
} |
223 |
|
224 |
/**
|
225 |
* Sets the pattern.
|
226 |
*/
|
227 |
public void setLiteral(String literal) { |
228 |
this.pattern = literal;
|
229 |
match = null;
|
230 |
} |
231 |
|
232 |
/**
|
233 |
* Determines whether or not a given feature matches this pattern.
|
234 |
*
|
235 |
* @param feature Specified feature to examine.
|
236 |
*
|
237 |
* @return Flag confirming whether or not this feature is inside the
|
238 |
* filter.
|
239 |
*
|
240 |
* @task REVISIT: could the pattern be null such that a null = null?
|
241 |
*/
|
242 |
public boolean evaluate(Object feature) { |
243 |
//Checks to ensure that the attribute has been set
|
244 |
if (attribute == null) { |
245 |
return false; |
246 |
} |
247 |
// Note that this converts the attribute to a string
|
248 |
// for comparison. Unlike the math or geometry filters, which
|
249 |
// require specific types to function correctly, this filter
|
250 |
// using the mandatory string representation in Java
|
251 |
// Of course, this does not guarantee a meaningful result, but it
|
252 |
// does guarantee a valid result.
|
253 |
////LOGGER.finest("pattern: " + pattern);
|
254 |
////LOGGER.finest("string: " + attribute.getValue(feature));
|
255 |
//return attribute.getValue(feature).toString().matches(pattern);
|
256 |
Object value = attribute.evaluate(feature);
|
257 |
|
258 |
if (null == value) { |
259 |
return false; |
260 |
} |
261 |
|
262 |
Matcher matcher = getMatcher();
|
263 |
matcher.reset(value.toString()); |
264 |
|
265 |
return matcher.matches();
|
266 |
} |
267 |
|
268 |
/**
|
269 |
* Return this filter as a string.
|
270 |
*
|
271 |
* @return String representation of this like filter.
|
272 |
*/
|
273 |
public String toString() { |
274 |
return "[ " + attribute.toString() + " is like " + pattern + " ]"; |
275 |
} |
276 |
|
277 |
/**
|
278 |
* Getter for property escape.
|
279 |
*
|
280 |
* @return Value of property escape.
|
281 |
*/
|
282 |
public java.lang.String getEscape() {
|
283 |
return escape;
|
284 |
} |
285 |
|
286 |
|
287 |
/**
|
288 |
* convienience method to determine if a character is special to the regex
|
289 |
* system.
|
290 |
*
|
291 |
* @param chr the character to test
|
292 |
*
|
293 |
* @return is the character a special character.
|
294 |
*/
|
295 |
private boolean isSpecial(final char chr) { |
296 |
return ((chr == '.') || (chr == '?') || (chr == '*') || (chr == '^') |
297 |
|| (chr == '$') || (chr == '+') || (chr == '[') || (chr == ']') |
298 |
|| (chr == '(') || (chr == ')') || (chr == '|') || (chr == '\\') |
299 |
|| (chr == '&'));
|
300 |
} |
301 |
|
302 |
/**
|
303 |
* convienience method to escape any character that is special to the regex
|
304 |
* system.
|
305 |
*
|
306 |
* @param inString the string to fix
|
307 |
*
|
308 |
* @return the fixed string
|
309 |
*/
|
310 |
private String fixSpecials(final String inString) { |
311 |
StringBuffer tmp = new StringBuffer(""); |
312 |
|
313 |
for (int i = 0; i < inString.length(); i++) { |
314 |
char chr = inString.charAt(i);
|
315 |
|
316 |
if (isSpecial(chr)) {
|
317 |
tmp.append(this.escape + chr);
|
318 |
} else {
|
319 |
tmp.append(chr); |
320 |
} |
321 |
} |
322 |
|
323 |
return tmp.toString();
|
324 |
} |
325 |
|
326 |
/**
|
327 |
* Compares this filter to the specified object. Returns true if the
|
328 |
* passed in object is the same as this filter. Checks to make sure the
|
329 |
* filter types, the value, and the pattern are the same. &
|
330 |
*
|
331 |
* @param obj - the object to compare this LikeFilter against.
|
332 |
*
|
333 |
* @return true if specified object is equal to this filter; false
|
334 |
* otherwise.
|
335 |
*/
|
336 |
public boolean equals(Object obj) { |
337 |
if (obj instanceof LikeFilterImpl) { |
338 |
LikeFilterImpl lFilter = (LikeFilterImpl) obj; |
339 |
|
340 |
//REVISIT: check for nulls.
|
341 |
return (lFilter.getLiteral().equals(this.getLiteral()) |
342 |
&& lFilter.getExpression().equals(this.getExpression()));
|
343 |
} |
344 |
return false; |
345 |
} |
346 |
|
347 |
/**
|
348 |
* Override of hashCode method.
|
349 |
*
|
350 |
* @return the hash code for this like filter implementation.
|
351 |
*/
|
352 |
public int hashCode() { |
353 |
int result = 17; |
354 |
result = (37 * result)
|
355 |
+ ((attribute == null) ? 0 : attribute.hashCode()); |
356 |
result = (37 * result) + ((pattern == null) ? 0 : pattern.hashCode()); |
357 |
|
358 |
return result;
|
359 |
} |
360 |
|
361 |
/**
|
362 |
* Used by FilterVisitors to perform some action on this filter instance.
|
363 |
* Typicaly used by Filter decoders, but may also be used by any thing
|
364 |
* which needs infomration from filter structure. Implementations should
|
365 |
* always call: visitor.visit(this); It is importatant that this is not
|
366 |
* left to a parent class unless the parents API is identical.
|
367 |
*
|
368 |
* @param visitor The visitor which requires access to this filter, the
|
369 |
* method must call visitor.visit(this);
|
370 |
*/
|
371 |
public Object accept(FilterVisitor visitor, Object extraData) { |
372 |
return visitor.visit(this,extraData); |
373 |
} |
374 |
|
375 |
public Expression getExpression() { |
376 |
return attribute;
|
377 |
} |
378 |
|
379 |
public String getSingleChar() { |
380 |
return wildcardSingle;
|
381 |
} |
382 |
|
383 |
public String getWildCard() { |
384 |
return wildcardMulti;
|
385 |
} |
386 |
} |