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    }