001/*
002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
003 * 
004 * This software is open source. 
005 * See the bottom of this file for the licence.
006 * 
007 * $Id: DefaultXPath.java,v 1.24 2002/03/13 03:29:55 jstrachan Exp $
008 */
009
010package org.dom4j.xpath;
011
012import org.dom4j.Document;
013import org.dom4j.Element;
014import org.dom4j.InvalidXPathException;
015import org.dom4j.Node;
016import org.dom4j.NodeFilter;
017import org.dom4j.XPath;
018import org.dom4j.XPathException;
019
020import org.dom4j.rule.Pattern;
021
022import org.jaxen.BaseXPath;
023import org.jaxen.FunctionContext;
024import org.jaxen.JaxenException;
025import org.jaxen.NamespaceContext;
026import org.jaxen.SimpleNamespaceContext;
027import org.jaxen.VariableContext;
028
029import org.saxpath.XPathReader;
030import org.saxpath.SAXPathException;
031import org.saxpath.helpers.XPathReaderFactory;
032
033import java.io.Serializable;
034import java.io.StringReader;
035
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.Comparator;
039import java.util.HashMap;
040import java.util.HashSet;
041import java.util.Iterator;
042import java.util.List;
043import java.util.Map;
044
045
046/** <p>Default implementation of {@link org.dom4j.XPath} which uses the
047 * <a href="http://jaxen.org">Jaxen</a> project.</p>
048 *
049 *  @author bob mcwhirter (bob @ werken.com)
050  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
051 */
052public class DefaultXPath implements org.dom4j.XPath, NodeFilter, Serializable {
053
054    private String text;
055    private BaseXPath xpath;
056    private NamespaceContext namespaceContext;
057    
058
059    /** Construct an XPath
060     */
061    public DefaultXPath(String text) throws InvalidXPathException {
062        this.text = text;
063        this.xpath = parse( text );
064    }
065
066    public String toString() {
067        return "[XPath: " + xpath + "]";
068    }
069
070    
071    // XPath interface 
072    
073    /** Retrieve the textual XPath string used to initialize this Object
074     *
075     *  @return The XPath string
076     */
077    public String getText() {
078        return text;
079    }
080
081    public FunctionContext getFunctionContext() {
082        return xpath.getFunctionContext();
083    }
084    
085    public void setFunctionContext(FunctionContext functionContext) {
086        xpath.setFunctionContext(functionContext);
087    }
088    
089    public NamespaceContext getNamespaceContext() {
090        return namespaceContext;
091    }
092    
093    public void setNamespaceURIs(Map map) {
094        setNamespaceContext( new SimpleNamespaceContext( map ) );
095    }
096    
097    public void setNamespaceContext(NamespaceContext namespaceContext) {
098        this.namespaceContext = namespaceContext;
099        xpath.setNamespaceContext(namespaceContext);
100    }
101    
102    public VariableContext getVariableContext() {
103        return xpath.getVariableContext();
104    }
105    
106    public void setVariableContext(VariableContext variableContext) {
107        xpath.setVariableContext(variableContext);
108    }
109    
110    public Object evaluate(Object context) {
111        try {
112            setNSContext(context);
113            List answer = xpath.selectNodes( context );
114            if ( answer != null && answer.size() == 1 ) {
115                return answer.get(0);
116            }
117            return answer;
118        }
119        catch (JaxenException e) {
120            handleJaxenException(e);
121            return null;
122        }
123    }
124
125    public Object selectObject(Object context) {
126        return evaluate(context);        
127    }
128    
129    public List selectNodes(Object context) {
130        try {
131            setNSContext(context);
132            return xpath.selectNodes( context );
133        }
134        catch (JaxenException e) {
135            handleJaxenException(e);
136            return Collections.EMPTY_LIST;
137        }
138    }
139    
140    
141    public List selectNodes(Object context, org.dom4j.XPath sortXPath) {
142        List answer = selectNodes( context );
143        sortXPath.sort( answer );
144        return answer;
145    }
146    
147    public List selectNodes(Object context, org.dom4j.XPath sortXPath, boolean distinct) {
148        List answer = selectNodes( context );
149        sortXPath.sort( answer, distinct );
150        return answer;
151    }
152    
153    public Node selectSingleNode(Object context) {
154        try {
155            setNSContext(context);
156            Object answer = xpath.selectSingleNode( context );
157            if ( answer instanceof Node ) {
158                return (Node) answer;
159            }
160            if ( answer == null ) {
161                return null;
162            }
163            throw new XPathException( 
164                "The result of the XPath expression is not a Node. It was: " 
165                + answer + " of type: " + answer.getClass().getName() 
166                + ". You might want to use a different method such as selectObject() to evaluate this XPath expression" 
167            );
168        }
169        catch (JaxenException e) {
170            handleJaxenException(e);
171            return null;
172        }
173    }
174    
175    
176    public String valueOf(Object context) {
177        try {
178            setNSContext(context);
179            return xpath.valueOf( context );
180        }
181        catch (JaxenException e) {
182            handleJaxenException(e);
183            return "";
184        }
185    }
186
187    public Number numberValueOf(Object context) {
188        try {
189            setNSContext(context);
190            return xpath.numberValueOf( context );
191        }
192        catch (JaxenException e) {
193            handleJaxenException(e);
194            return null;
195        }
196    }
197    
198    /** <p><code>sort</code> sorts the given List of Nodes
199      * using this XPath expression as a {@link Comparator}.</p>
200      *
201      * @param list is the list of Nodes to sort
202      */
203    public void sort( List list ) {
204        sort( list, false );
205    }
206    
207    /** <p><code>sort</code> sorts the given List of Nodes
208      * using this XPath expression as a {@link Comparator} 
209      * and optionally removing duplicates.</p>
210      *
211      * @param list is the list of Nodes to sort
212      * @param distinct if true then duplicate values (using the sortXPath for 
213      *     comparisions) will be removed from the List
214      */
215    public void sort( List list, boolean distinct ) {
216        if ( list != null && ! list.isEmpty() )  {
217            int size = list.size();
218            HashMap sortValues = new HashMap( size );
219            for ( int i = 0; i < size; i++ ) {
220                Object object = list.get(i);
221                if ( object instanceof Node ) {
222                    Node node = (Node) object;
223                    Object expression = getCompareValue(node);
224                    sortValues.put(node, expression);
225                }
226            }
227            sort( list, sortValues );
228
229            if (distinct) {
230                removeDuplicates( list, sortValues );
231            }
232        }
233    }
234    
235    public boolean matches( Node node ) {
236        try {
237            setNSContext(node);
238            List answer = xpath.selectNodes( node );
239            if ( answer != null && answer.size() > 0 ) {
240                Object item = answer.get(0);
241                if ( item instanceof Boolean ) {
242                    return ((Boolean) item).booleanValue();
243                }
244                return answer.contains( node );
245            }
246            return false;
247        }
248        catch (JaxenException e) {
249            handleJaxenException(e);
250            return false;
251        }
252    }
253    
254    /** Sorts the list based on the sortValues for each node
255      */
256    protected void sort( List list, final Map sortValues ) {
257        Collections.sort(
258            list,
259            new Comparator() {
260                public int compare(Object o1, Object o2) {
261                    o1 = sortValues.get(o1);
262                    o2 = sortValues.get(o2);
263                    if ( o1 == o2 ) {
264                        return 0;
265                    }
266                    else if ( o1 instanceof Comparable )  {
267                        Comparable c1 = (Comparable) o1;
268                        return c1.compareTo(o2);
269                    }
270                    else if ( o1 == null )  {
271                        return 1;
272                    }
273                    else if ( o2 == null ) {
274                        return -1;
275                    }
276                    else {
277                        return o1.equals(o2) ? 0 : -1;
278                    }
279                }
280            }
281        );
282    }
283
284    // Implementation methods
285    
286    
287    
288    /** Removes items from the list which have duplicate values
289      */
290    protected void removeDuplicates( List list, Map sortValues ) {
291        // remove distinct
292        HashSet distinctValues = new HashSet();
293        for ( Iterator iter = list.iterator(); iter.hasNext(); ) {
294            Object node = iter.next();
295            Object value = sortValues.get(node);
296            if ( distinctValues.contains( value ) ) {
297                iter.remove();
298            }
299            else {
300                distinctValues.add( value );
301            }
302        }
303    }
304    
305    /** @return the node expression used for sorting comparisons
306      */
307    protected Object getCompareValue(Node node) {
308        return valueOf( node );
309    }
310    
311    protected static BaseXPath parse(String text) {        
312        try {
313            return new org.jaxen.dom4j.XPath( text );
314        }
315        catch (SAXPathException e) {
316            throw new InvalidXPathException( text, e.getMessage() );
317        }
318        catch (RuntimeException e) {
319        }
320        throw new InvalidXPathException( text );
321    }
322    
323    protected void setNSContext(Object context) {
324        if ( namespaceContext == null ) {
325            xpath.setNamespaceContext( DefaultNamespaceContext.create(context) );
326        }
327    }
328        
329    protected void handleJaxenException(JaxenException e) throws XPathException {
330        throw new XPathException(text, e);
331    }
332}
333
334
335
336
337/*
338 * Redistribution and use of this software and associated documentation
339 * ("Software"), with or without modification, are permitted provided
340 * that the following conditions are met:
341 *
342 * 1. Redistributions of source code must retain copyright
343 *    statements and notices.  Redistributions must also contain a
344 *    copy of this document.
345 *
346 * 2. Redistributions in binary form must reproduce the
347 *    above copyright notice, this list of conditions and the
348 *    following disclaimer in the documentation and/or other
349 *    materials provided with the distribution.
350 *
351 * 3. The name "DOM4J" must not be used to endorse or promote
352 *    products derived from this Software without prior written
353 *    permission of MetaStuff, Ltd.  For written permission,
354 *    please contact dom4j-info@metastuff.com.
355 *
356 * 4. Products derived from this Software may not be called "DOM4J"
357 *    nor may "DOM4J" appear in their names without prior written
358 *    permission of MetaStuff, Ltd. DOM4J is a registered
359 *    trademark of MetaStuff, Ltd.
360 *
361 * 5. Due credit should be given to the DOM4J Project
362 *    (http://dom4j.org/).
363 *
364 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
365 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
366 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
367 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
368 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
369 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
370 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
371 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
372 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
373 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
374 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
375 * OF THE POSSIBILITY OF SUCH DAMAGE.
376 *
377 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
378 *
379 * $Id: DefaultXPath.java,v 1.24 2002/03/13 03:29:55 jstrachan Exp $
380 */