001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/model/filterencoding/PropertyIsLikeOperation.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.model.filterencoding;
037
038 import org.deegree.framework.xml.ElementList;
039 import org.deegree.framework.xml.XMLTools;
040 import org.deegree.model.feature.Feature;
041 import org.w3c.dom.Element;
042
043 /**
044 * Encapsulates the information of a <PropertyIsLike>-element (as defined in Filter DTD).
045 *
046 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
047 * @author last edited by: $Author: apoth $
048 *
049 * @version $Revision: 22287 $, $Date: 2010-01-27 19:56:34 +0100 (Mi, 27. Jan 2010) $
050 */
051 public class PropertyIsLikeOperation extends ComparisonOperation {
052
053 private PropertyName propertyName;
054
055 private Literal literal;
056
057 // attributes of <PropertyIsLike>
058 private char wildCard;
059
060 private char singleChar;
061
062 private char escapeChar;
063
064 private boolean matchCase;
065
066 /**
067 *
068 * @param propertyName
069 * @param literal
070 * @param wildCard
071 * @param singleChar
072 * @param escapeChar
073 */
074 public PropertyIsLikeOperation( PropertyName propertyName, Literal literal, char wildCard, char singleChar,
075 char escapeChar ) {
076 this( propertyName, literal, wildCard, singleChar, escapeChar, true );
077 }
078
079 /**
080 *
081 * @param propertyName
082 * @param literal
083 * @param wildCard
084 * @param singleChar
085 * @param escapeChar
086 * @param matchCase
087 */
088 public PropertyIsLikeOperation( PropertyName propertyName, Literal literal, char wildCard, char singleChar,
089 char escapeChar, boolean matchCase ) {
090 super( OperationDefines.PROPERTYISLIKE );
091 this.propertyName = propertyName;
092 this.literal = literal;
093 this.wildCard = wildCard;
094 this.singleChar = singleChar;
095 this.escapeChar = escapeChar;
096 this.matchCase = matchCase;
097 }
098
099 /**
100 * @return the wildcard
101 */
102 public char getWildCard() {
103 return wildCard;
104 }
105
106 /**
107 * @return the singleChar
108 */
109 public char getSingleChar() {
110 return singleChar;
111 }
112
113 /**
114 * @return the escape character
115 */
116 public char getEscapeChar() {
117 return escapeChar;
118 }
119
120 /**
121 * @return matchCase flag
122 */
123 public boolean isMatchCase() {
124 return matchCase;
125 }
126
127 /**
128 * Given a DOM-fragment, a corresponding Operation-object is built. This method recursively calls other buildFromDOM
129 * () - methods to validate the structure of the DOM-fragment.
130 *
131 * @param element
132 * the element to parse
133 * @return a Bean of the DOM
134 *
135 * @throws FilterConstructionException
136 * if the structure of the DOM-fragment is invalid
137 */
138 public static Operation buildFromDOM( Element element )
139 throws FilterConstructionException {
140
141 // check if root element's name equals 'PropertyIsLike'
142 if ( !element.getLocalName().equals( "PropertyIsLike" ) ) {
143 throw new FilterConstructionException( "Name of element does not equal 'PropertyIsLike'!" );
144 }
145
146 ElementList children = XMLTools.getChildElements( element );
147 if ( children.getLength() != 2 ) {
148 throw new FilterConstructionException( "'PropertyIsLike' requires exactly 2 elements!" );
149 }
150
151 PropertyName propertyName = (PropertyName) PropertyName.buildFromDOM( children.item( 0 ) );
152 Literal literal = (Literal) Literal.buildFromDOM( children.item( 1 ) );
153
154 // determine the needed attributes
155 String wildCard = element.getAttribute( "wildCard" );
156 if ( wildCard == null || wildCard.length() == 0 ) {
157 throw new FilterConstructionException( "wildCard-Attribute is unspecified!" );
158 }
159 if ( wildCard.length() != 1 ) {
160 throw new FilterConstructionException( "wildCard-Attribute must be exactly one character!" );
161 }
162
163 // This shouldn't be necessary and can actually cause problems...
164 // // always use '%' as wildcard because this is compliant to SQL databases
165 // literal = new Literal( StringTools.replace( literal.getValue(), wildCard, "%", true ) );
166 // wildCard = "%";
167 String singleChar = element.getAttribute( "singleChar" );
168 if ( singleChar == null || singleChar.length() == 0 ) {
169 throw new FilterConstructionException( "singleChar-Attribute is unspecified!" );
170 }
171 if ( singleChar.length() != 1 ) {
172 throw new FilterConstructionException( "singleChar-Attribute must be exactly one character!" );
173 }
174 String escapeChar = element.getAttribute( "escape" );
175 if ( escapeChar == null || escapeChar.length() == 0 ) {
176 escapeChar = element.getAttribute( "escapeChar" );
177 }
178 if ( escapeChar == null || escapeChar.length() == 0 ) {
179 throw new FilterConstructionException( "escape-Attribute is unspecified!" );
180 }
181 if ( escapeChar.length() != 1 ) {
182 throw new FilterConstructionException( "escape-Attribute must be exactly one character!" );
183 }
184 boolean matchCase = true;
185 String tmp = element.getAttribute( "matchCase" );
186 if ( tmp != null && tmp.length() > 0 ) {
187 try {
188 matchCase = Boolean.parseBoolean( tmp );
189 } catch ( Exception e ) {
190 // undocumented
191 }
192
193 }
194
195 return new PropertyIsLikeOperation( propertyName, literal, wildCard.charAt( 0 ), singleChar.charAt( 0 ),
196 escapeChar.charAt( 0 ), matchCase );
197 }
198
199 /**
200 * @return the name of the property that shall be compared to the literal
201 *
202 */
203 public PropertyName getPropertyName() {
204 return propertyName;
205 }
206
207 /**
208 * @return the literal the property shall be compared to
209 */
210 public Literal getLiteral() {
211 return literal;
212 }
213
214 /**
215 * @deprecated use {@link #to100XML()} or {@link #to110XML()} instead; default behavior is {@link #to110XML()}
216 */
217 public StringBuffer toXML() {
218 return to110XML();
219 }
220
221 public StringBuffer to100XML() {
222 StringBuffer sb = new StringBuffer( 500 );
223 sb.append( "<ogc:" ).append( getOperatorName() ).append( " wildCard=\"" ).append( wildCard );
224 sb.append( "\" singleChar=\"" ).append( singleChar ).append( "\" escape=\"" );
225 sb.append( escapeChar ).append( "\" matchCase=\"" ).append( matchCase ).append( "\">" );
226 sb.append( propertyName.toXML() ).append( literal.toXML() );
227 sb.append( "</ogc:" ).append( getOperatorName() ).append( ">" );
228 return sb;
229 }
230
231 public StringBuffer to110XML() {
232 StringBuffer sb = new StringBuffer( 500 );
233 sb.append( "<ogc:" ).append( getOperatorName() ).append( " wildCard=\"" ).append( wildCard );
234 sb.append( "\" singleChar=\"" ).append( singleChar ).append( "\" escapeChar=\"" );
235 sb.append( escapeChar ).append( "\" matchCase=\"" ).append( matchCase ).append( "\">" );
236 sb.append( propertyName.toXML() ).append( literal.toXML() );
237 sb.append( "</ogc:" ).append( getOperatorName() ).append( ">" );
238 return sb;
239 }
240
241 /**
242 * Calculates the <tt>PropertyIsLike</tt>'s logical value based on the certain property values of the given
243 * <tt>Feature</tt>.
244 * <p>
245 *
246 * @param feature
247 * that determines the property values
248 * @return true, if the <tt>Literal</tt> matches the <tt>PropertyName</tt>'s value
249 * @throws FilterEvaluationException
250 * if the evaluation could not be performed (for example a specified Property did not exist)
251 */
252 public boolean evaluate( Feature feature )
253 throws FilterEvaluationException {
254
255 Object value1 = null;
256 Object value2 = null;
257 try {
258 value1 = propertyName.evaluate( feature );
259 value2 = literal.getValue();
260 } catch ( Exception e ) {
261 e.printStackTrace();
262 }
263 if ( value1 == null && value2 == null ) {
264 return true;
265 } else if ( value1 == null || value2 == null ) {
266 return false;
267 }
268 if ( isMatchCase() ) {
269 return matches( value2.toString(), value1.toString() );
270 }
271 return matches( value2.toString().toUpperCase(), value1.toString().toUpperCase() );
272 }
273
274 /**
275 * Checks if a given <tt>String<tt> matches a pattern that is a sequence
276 * of:
277 * <ul>
278 * <li>standard characters</li>
279 * <li>wildcard characters (like * in most shells)</li>
280 * <li>singlechar characters (like ? in most shells)</li>
281 * </ul>
282 *
283 * @param pattern
284 * the pattern to compare to
285 * @param buffer
286 * the <tt>String</tt> to test
287 * @return true, if the <tt>String</tt> matches the pattern
288 */
289 public boolean matches( String pattern, String buffer ) {
290 // match was successful if both the pattern and the buffer are empty
291 if ( pattern.length() == 0 && buffer.length() == 0 )
292 return true;
293
294 // build the prefix that has to match the beginning of the buffer
295 // prefix ends at the first (unescaped!) wildcard / singlechar character
296 StringBuffer sb = new StringBuffer();
297 boolean escapeMode = false;
298 int length = pattern.length();
299 char specialChar = '\0';
300
301 for ( int i = 0; i < length; i++ ) {
302 char c = pattern.charAt( i );
303
304 if ( escapeMode ) {
305 // just append every character (except the escape character)
306 if ( c != escapeChar )
307 sb.append( c );
308 escapeMode = false;
309 } else {
310 // escapeChar means: switch to escapeMode
311 if ( c == escapeChar )
312 escapeMode = true;
313 // wildCard / singleChar means: prefix ends here
314 else if ( c == wildCard || c == singleChar ) {
315 specialChar = c;
316 break;
317 } else
318 sb.append( c );
319 }
320 }
321 String prefix = sb.toString();
322 int skip = prefix.length();
323
324 // the buffer must begin with the prefix or else there is no match
325 if ( !buffer.startsWith( prefix ) )
326 return false;
327
328 if ( specialChar == wildCard ) {
329 // the prefix is terminated by a wildcard-character
330 pattern = pattern.substring( skip + 1, pattern.length() );
331 // try to find a match for the rest of the pattern
332 for ( int i = skip; i <= buffer.length(); i++ ) {
333 String rest = buffer.substring( i, buffer.length() );
334 if ( matches( pattern, rest ) )
335 return true;
336 }
337 } else if ( specialChar == singleChar ) {
338 // the prefix is terminated by a singlechar-character
339 pattern = pattern.substring( skip + 1, pattern.length() );
340 if ( skip + 1 > buffer.length() )
341 return false;
342 String rest = buffer.substring( skip + 1, buffer.length() );
343 if ( matches( pattern, rest ) )
344 return true;
345 } else if ( specialChar == '\0' ) {
346 // the prefix is terminated by the end of the pattern
347 if ( buffer.length() == prefix.length() )
348 return true;
349 }
350 return false;
351 }
352 }