View Javadoc

    1   /*
    2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    3    *
    4    * This software is open source.
    5    * See the bottom of this file for the licence.
    6    */
    7
    8   package org.dom4j.xpath;
    9
    10  import java.io.Serializable;
    11  import java.util.Collections;
    12  import java.util.Comparator;
    13  import java.util.HashMap;
    14  import java.util.HashSet;
    15  import java.util.Iterator;
    16  import java.util.List;
    17  import java.util.Map;
    18
    19  import org.dom4j.InvalidXPathException;
    20  import org.dom4j.Node;
    21  import org.dom4j.NodeFilter;
    22  import org.dom4j.XPathException;
    23
    24  import org.jaxen.FunctionContext;
    25  import org.jaxen.JaxenException;
    26  import org.jaxen.NamespaceContext;
    27  import org.jaxen.SimpleNamespaceContext;
    28  import org.jaxen.VariableContext;
    29  import org.jaxen.XPath;
    30  import org.jaxen.dom4j.Dom4jXPath;
    31
    32  /***
    33   * <p>
    34   * Default implementation of {@link org.dom4j.XPath}which uses the <a
    35   * href="http://jaxen.org">Jaxen </a> project.
    36   * </p>
    37   *
    38   * @author bob mcwhirter
    39   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
    40   */
    41  public class DefaultXPath implements org.dom4j.XPath, NodeFilter, Serializable {
    42      private String text;
    43
    44      private XPath xpath;
    45
    46      private NamespaceContext namespaceContext;
    47
    48      /***
    49       * Construct an XPath
    50       *
    51       * @param text
    52       *            DOCUMENT ME!
    53       *
    54       * @throws InvalidXPathException
    55       *             DOCUMENT ME!
    56       */
    57      public DefaultXPath(String text) throws InvalidXPathException {
    58          this.text = text;
    59          this.xpath = parse(text);
    60      }
    61
    62      public String toString() {
    63          return "[XPath: " + xpath + "]";
    64      }
    65
    66      // XPath interface
    67
    68      /***
    69       * Retrieve the textual XPath string used to initialize this Object
    70       *
    71       * @return The XPath string
    72       */
    73      public String getText() {
    74          return text;
    75      }
    76
    77      public FunctionContext getFunctionContext() {
    78          return xpath.getFunctionContext();
    79      }
    80
    81      public void setFunctionContext(FunctionContext functionContext) {
    82          xpath.setFunctionContext(functionContext);
    83      }
    84
    85      public NamespaceContext getNamespaceContext() {
    86          return namespaceContext;
    87      }
    88
    89      public void setNamespaceURIs(Map map) {
    90          setNamespaceContext(new SimpleNamespaceContext(map));
    91      }
    92
    93      public void setNamespaceContext(NamespaceContext namespaceContext) {
    94          this.namespaceContext = namespaceContext;
    95          xpath.setNamespaceContext(namespaceContext);
    96      }
    97
    98      public VariableContext getVariableContext() {
    99          return xpath.getVariableContext();
    100     }
    101
    102     public void setVariableContext(VariableContext variableContext) {
    103         xpath.setVariableContext(variableContext);
    104     }
    105
    106     public Object evaluate(Object context) {
    107         try {
    108             setNSContext(context);
    109
    110             List answer = xpath.selectNodes(context);
    111
    112             if ((answer != null) && (answer.size() == 1)) {
    113                 return answer.get(0);
    114             }
    115
    116             return answer;
    117         } catch (JaxenException e) {
    118             handleJaxenException(e);
    119
    120             return null;
    121         }
    122     }
    123
    124     public Object selectObject(Object context) {
    125         return evaluate(context);
    126     }
    127
    128     public List selectNodes(Object context) {
    129         try {
    130             setNSContext(context);
    131
    132             return xpath.selectNodes(context);
    133         } catch (JaxenException e) {
    134             handleJaxenException(e);
    135
    136             return Collections.EMPTY_LIST;
    137         }
    138     }
    139
    140     public List selectNodes(Object context, org.dom4j.XPath sortXPath) {
    141         List answer = selectNodes(context);
    142         sortXPath.sort(answer);
    143
    144         return answer;
    145     }
    146
    147     public List selectNodes(Object context, org.dom4j.XPath sortXPath,
    148             boolean distinct) {
    149         List answer = selectNodes(context);
    150         sortXPath.sort(answer, distinct);
    151
    152         return answer;
    153     }
    154
    155     public Node selectSingleNode(Object context) {
    156         try {
    157             setNSContext(context);
    158
    159             Object answer = xpath.selectSingleNode(context);
    160
    161             if (answer instanceof Node) {
    162                 return (Node) answer;
    163             }
    164
    165             if (answer == null) {
    166                 return null;
    167             }
    168
    169             throw new XPathException("The result of the XPath expression is "
    170                     + "not a Node. It was: " + answer + " of type: "
    171                     + answer.getClass().getName());
    172         } catch (JaxenException e) {
    173             handleJaxenException(e);
    174
    175             return null;
    176         }
    177     }
    178
    179     public String valueOf(Object context) {
    180         try {
    181             setNSContext(context);
    182
    183             return xpath.stringValueOf(context);
    184         } catch (JaxenException e) {
    185             handleJaxenException(e);
    186
    187             return "";
    188         }
    189     }
    190
    191     public Number numberValueOf(Object context) {
    192         try {
    193             setNSContext(context);
    194
    195             return xpath.numberValueOf(context);
    196         } catch (JaxenException e) {
    197             handleJaxenException(e);
    198
    199             return null;
    200         }
    201     }
    202
    203     public boolean booleanValueOf(Object context) {
    204         try {
    205             setNSContext(context);
    206
    207             return xpath.booleanValueOf(context);
    208         } catch (JaxenException e) {
    209             handleJaxenException(e);
    210
    211             return false;
    212         }
    213     }
    214
    215     /***
    216      * <p>
    217      * <code>sort</code> sorts the given List of Nodes using this XPath
    218      * expression as a {@link Comparator}.
    219      * </p>
    220      *
    221      * @param list
    222      *            is the list of Nodes to sort
    223      */
    224     public void sort(List list) {
    225         sort(list, false);
    226     }
    227
    228     /***
    229      * <p>
    230      * <code>sort</code> sorts the given List of Nodes using this XPath
    231      * expression as a {@link Comparator}and optionally removing duplicates.
    232      * </p>
    233      *
    234      * @param list
    235      *            is the list of Nodes to sort
    236      * @param distinct
    237      *            if true then duplicate values (using the sortXPath for
    238      *            comparisions) will be removed from the List
    239      */
    240     public void sort(List list, boolean distinct) {
    241         if ((list != null) && !list.isEmpty()) {
    242             int size = list.size();
    243             HashMap sortValues = new HashMap(size);
    244
    245             for (int i = 0; i < size; i++) {
    246                 Object object = list.get(i);
    247
    248                 if (object instanceof Node) {
    249                     Node node = (Node) object;
    250                     Object expression = getCompareValue(node);
    251                     sortValues.put(node, expression);
    252                 }
    253             }
    254
    255             sort(list, sortValues);
    256
    257             if (distinct) {
    258                 removeDuplicates(list, sortValues);
    259             }
    260         }
    261     }
    262
    263     public boolean matches(Node node) {
    264         try {
    265             setNSContext(node);
    266
    267             List answer = xpath.selectNodes(node);
    268
    269             if ((answer != null) && (answer.size() > 0)) {
    270                 Object item = answer.get(0);
    271
    272                 if (item instanceof Boolean) {
    273                     return ((Boolean) item).booleanValue();
    274                 }
    275
    276                 return answer.contains(node);
    277             }
    278
    279             return false;
    280         } catch (JaxenException e) {
    281             handleJaxenException(e);
    282
    283             return false;
    284         }
    285     }
    286
    287     /***
    288      * Sorts the list based on the sortValues for each node
    289      *
    290      * @param list
    291      *            DOCUMENT ME!
    292      * @param sortValues
    293      *            DOCUMENT ME!
    294      */
    295     protected void sort(List list, final Map sortValues) {
    296         Collections.sort(list, new Comparator() {
    297             public int compare(Object o1, Object o2) {
    298                 o1 = sortValues.get(o1);
    299                 o2 = sortValues.get(o2);
    300
    301                 if (o1 == o2) {
    302                     return 0;
    303                 } else if (o1 instanceof Comparable) {
    304                     Comparable c1 = (Comparable) o1;
    305
    306                     return c1.compareTo(o2);
    307                 } else if (o1 == null) {
    308                     return 1;
    309                 } else if (o2 == null) {
    310                     return -1;
    311                 } else {
    312                     return o1.equals(o2) ? 0 : (-1);
    313                 }
    314             }
    315         });
    316     }
    317
    318     // Implementation methods
    319
    320     /***
    321      * Removes items from the list which have duplicate values
    322      *
    323      * @param list
    324      *            DOCUMENT ME!
    325      * @param sortValues
    326      *            DOCUMENT ME!
    327      */
    328     protected void removeDuplicates(List list, Map sortValues) {
    329         // remove distinct
    330         HashSet distinctValues = new HashSet();
    331
    332         for (Iterator iter = list.iterator(); iter.hasNext();) {
    333             Object node = iter.next();
    334             Object value = sortValues.get(node);
    335
    336             if (distinctValues.contains(value)) {
    337                 iter.remove();
    338             } else {
    339                 distinctValues.add(value);
    340             }
    341         }
    342     }
    343
    344     /***
    345      * DOCUMENT ME!
    346      *
    347      * @param node
    348      *            DOCUMENT ME!
    349      *
    350      * @return the node expression used for sorting comparisons
    351      */
    352     protected Object getCompareValue(Node node) {
    353         return valueOf(node);
    354     }
    355
    356     protected static XPath parse(String text) {
    357         try {
    358             return new Dom4jXPath(text);
    359         } catch (JaxenException e) {
    360             throw new InvalidXPathException(text, e.getMessage());
    361         } catch (Throwable t) {
    362         	throw new InvalidXPathException(text, t);
    363         }
    364     }
    365
    366     protected void setNSContext(Object context) {
    367         if (namespaceContext == null) {
    368             xpath.setNamespaceContext(DefaultNamespaceContext.create(context));
    369         }
    370     }
    371
    372     protected void handleJaxenException(JaxenException exception)
    373             throws XPathException {
    374         throw new XPathException(text, exception);
    375     }
    376 }
    377
    378 /*
    379  * Redistribution and use of this software and associated documentation
    380  * ("Software"), with or without modification, are permitted provided that the
    381  * following conditions are met:
    382  *
    383  * 1. Redistributions of source code must retain copyright statements and
    384  * notices. Redistributions must also contain a copy of this document.
    385  *
    386  * 2. Redistributions in binary form must reproduce the above copyright notice,
    387  * this list of conditions and the following disclaimer in the documentation
    388  * and/or other materials provided with the distribution.
    389  *
    390  * 3. The name "DOM4J" must not be used to endorse or promote products derived
    391  * from this Software without prior written permission of MetaStuff, Ltd. For
    392  * written permission, please contact dom4j-info@metastuff.com.
    393  *
    394  * 4. Products derived from this Software may not be called "DOM4J" nor may
    395  * "DOM4J" appear in their names without prior written permission of MetaStuff,
    396  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
    397  *
    398  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
    399  *
    400  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
    401  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    402  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    403  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
    404  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    405  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    406  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    407  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    408  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    409  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    410  * POSSIBILITY OF SUCH DAMAGE.
    411  *
    412  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    413  */