Statistics
| Revision:

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
}