001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }