001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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: mschneider $
048     *
049     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
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
129         * calls other buildFromDOM () - 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        public StringBuffer toXML() {
215            StringBuffer sb = new StringBuffer( 500 );
216            sb.append( "<ogc:" ).append( getOperatorName() ).append( " wildCard=\"" ).append( wildCard );
217            sb.append( "\" singleChar=\"" ).append( singleChar ).append( "\" escape=\"" );
218            sb.append( escapeChar ).append( "\" matchCase=\"" ).append( matchCase ).append( "\">" );
219            sb.append( propertyName.toXML() ).append( literal.toXML() );
220            sb.append( "</ogc:" ).append( getOperatorName() ).append( ">" );
221            return sb;
222        }
223    
224        public StringBuffer to100XML() {
225            return toXML();
226        }
227    
228        public StringBuffer to110XML() {
229            return toXML();
230        }
231    
232        /**
233         * Calculates the <tt>PropertyIsLike</tt>'s logical value based on the certain property
234         * values of the given <tt>Feature</tt>.
235         * <p>
236         *
237         * @param feature
238         *            that determines the property values
239         * @return true, if the <tt>Literal</tt> matches the <tt>PropertyName</tt>'s value
240         * @throws FilterEvaluationException
241         *             if the evaluation could not be performed (for example a specified Property did
242         *             not exist)
243         */
244        public boolean evaluate( Feature feature )
245                                throws FilterEvaluationException {
246    
247            Object value1 = null;
248            Object value2 = null;
249            try {
250                value1 = propertyName.evaluate( feature );
251                value2 = literal.getValue();
252            } catch ( Exception e ) {
253                e.printStackTrace();
254            }
255            if ( value1 == null && value2 == null ) {
256                return true;
257            } else if ( value1 == null || value2 == null ) {
258                return false;
259            }
260            if ( isMatchCase() ) {
261                return matches( value2.toString(), value1.toString() );
262            }
263            return matches( value2.toString().toUpperCase(), value1.toString().toUpperCase() );
264        }
265    
266        /**
267         * Checks if a given <tt>String<tt> matches a pattern that is a sequence
268         * of:
269         * <ul>
270         *   <li>standard characters</li>
271         *   <li>wildcard characters (like * in most shells)</li>
272         *   <li>singlechar characters (like ? in most shells)</li>
273         * </ul>
274         * @param pattern the pattern to compare to
275         * @param buffer the <tt>String</tt> to test
276         * @return true, if the <tt>String</tt> matches the pattern
277         */
278        public boolean matches( String pattern, String buffer ) {
279            // match was successful if both the pattern and the buffer are empty
280            if ( pattern.length() == 0 && buffer.length() == 0 )
281                return true;
282    
283            // build the prefix that has to match the beginning of the buffer
284            // prefix ends at the first (unescaped!) wildcard / singlechar character
285            StringBuffer sb = new StringBuffer();
286            boolean escapeMode = false;
287            int length = pattern.length();
288            char specialChar = '\0';
289    
290            for ( int i = 0; i < length; i++ ) {
291                char c = pattern.charAt( i );
292    
293                if ( escapeMode ) {
294                    // just append every character (except the escape character)
295                    if ( c != escapeChar )
296                        sb.append( c );
297                    escapeMode = false;
298                } else {
299                    // escapeChar means: switch to escapeMode
300                    if ( c == escapeChar )
301                        escapeMode = true;
302                    // wildCard / singleChar means: prefix ends here
303                    else if ( c == wildCard || c == singleChar ) {
304                        specialChar = c;
305                        break;
306                    } else
307                        sb.append( c );
308                }
309            }
310            String prefix = sb.toString();
311            int skip = prefix.length();
312    
313            // the buffer must begin with the prefix or else there is no match
314            if ( !buffer.startsWith( prefix ) )
315                return false;
316    
317            if ( specialChar == wildCard ) {
318                // the prefix is terminated by a wildcard-character
319                pattern = pattern.substring( skip + 1, pattern.length() );
320                // try to find a match for the rest of the pattern
321                for ( int i = skip; i <= buffer.length(); i++ ) {
322                    String rest = buffer.substring( i, buffer.length() );
323                    if ( matches( pattern, rest ) )
324                        return true;
325                }
326            } else if ( specialChar == singleChar ) {
327                // the prefix is terminated by a singlechar-character
328                pattern = pattern.substring( skip + 1, pattern.length() );
329                if ( skip + 1 > buffer.length() )
330                    return false;
331                String rest = buffer.substring( skip + 1, buffer.length() );
332                if ( matches( pattern, rest ) )
333                    return true;
334            } else if ( specialChar == '\0' ) {
335                // the prefix is terminated by the end of the pattern
336                if ( buffer.length() == prefix.length() )
337                    return true;
338            }
339            return false;
340        }
341    }