View Javadoc

    1   /*
    2    * Copyright 1999-2004 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jxpath.ri.model;
    17
    18  import java.util.Locale;
    19
    20  import org.apache.commons.jxpath.JXPathContext;
    21  import org.apache.commons.jxpath.JXPathException;
    22  import org.apache.commons.jxpath.Pointer;
    23  import org.apache.commons.jxpath.ri.Compiler;
    24  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
    25  import org.apache.commons.jxpath.ri.NamespaceResolver;
    26  import org.apache.commons.jxpath.ri.QName;
    27  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
    28  import org.apache.commons.jxpath.ri.compiler.NodeTest;
    29  import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
    30  import org.apache.commons.jxpath.ri.model.beans.NullPointer;
    31
    32  /***
    33   * Common superclass for Pointers of all kinds.  A NodePointer maps to
    34   * a deterministic XPath that represents the location of a node in an
    35   * object graph. This XPath uses only simple axes: child, namespace and
    36   * attribute and only simple, context-independent predicates.
    37   *
    38   * @author Dmitri Plotnikov
    39   * @version $Revision: 1.25 $ $Date: 2004/04/01 02:55:32 $
    40   */
    41  public abstract class NodePointer implements Pointer {
    42
    43      public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
    44      protected int index = WHOLE_COLLECTION;
    45      public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
    46      private boolean attribute = false;
    47      private transient Object rootNode;
    48      private NamespaceResolver namespaceResolver;
    49
    50      /***
    51       * Allocates an entirely new NodePointer by iterating through all installed
    52       * NodePointerFactories until it finds one that can create a pointer.
    53       */
    54      public static NodePointer newNodePointer(
    55          QName name,
    56          Object bean,
    57          Locale locale)
    58      {
    59          NodePointer pointer = null;
    60          if (bean == null) {
    61              pointer = new NullPointer(name, locale);
    62              return pointer;
    63          }
    64
    65          NodePointerFactory[] factories =
    66              JXPathContextReferenceImpl.getNodePointerFactories();
    67          for (int i = 0; i < factories.length; i++) {
    68              pointer = factories[i].createNodePointer(name, bean, locale);
    69              if (pointer != null) {
    70                  return pointer;
    71              }
    72          }
    73          throw new JXPathException(
    74              "Could not allocate a NodePointer for object of "
    75                  + bean.getClass());
    76      }
    77
    78      /***
    79       * Allocates an new child NodePointer by iterating through all installed
    80       * NodePointerFactories until it finds one that can create a pointer.
    81       */
    82      public static NodePointer newChildNodePointer(
    83          NodePointer parent,
    84          QName name,
    85          Object bean)
    86      {
    87          NodePointerFactory[] factories =
    88              JXPathContextReferenceImpl.getNodePointerFactories();
    89          for (int i = 0; i < factories.length; i++) {
    90              NodePointer pointer =
    91                  factories[i].createNodePointer(parent, name, bean);
    92              if (pointer != null) {
    93                  return pointer;
    94              }
    95          }
    96          throw new JXPathException(
    97              "Could not allocate a NodePointer for object of "
    98                  + bean.getClass());
    99      }
    100
    101     protected NodePointer parent;
    102     protected Locale locale;
    103 //    private NamespaceManager namespaceManager;
    104
    105     protected NodePointer(NodePointer parent) {
    106         this.parent = parent;
    107     }
    108
    109     protected NodePointer(NodePointer parent, Locale locale) {
    110         this.parent = parent;
    111         this.locale = locale;
    112     }
    113
    114     public NamespaceResolver getNamespaceResolver() {
    115         if (namespaceResolver == null && parent != null) {
    116             namespaceResolver = parent.getNamespaceResolver();
    117         }
    118         return namespaceResolver;
    119     }
    120
    121     public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
    122         this.namespaceResolver = namespaceResolver;
    123     }
    124
    125     public NodePointer getParent() {
    126         NodePointer pointer = parent;
    127         while (pointer != null && pointer.isContainer()) {
    128             pointer = pointer.getImmediateParentPointer();
    129         }
    130         return pointer;
    131     }
    132
    133     public NodePointer getImmediateParentPointer() {
    134         return parent;
    135     }
    136
    137     /***
    138      * Set to true if the pointer represents the "attribute::" axis.
    139      */
    140     public void setAttribute(boolean attribute) {
    141         this.attribute = attribute;
    142     }
    143
    144     /***
    145      * Returns true if the pointer represents the "attribute::" axis.
    146      */
    147     public boolean isAttribute() {
    148         return attribute;
    149     }
    150
    151     /***
    152      * Returns true if this Pointer has no parent.
    153      */
    154     public boolean isRoot() {
    155         return parent == null;
    156     }
    157
    158     /***
    159      * If true, this node does not have children
    160      */
    161     public abstract boolean isLeaf();
    162
    163     /***
    164      * @deprecated Please use !isContainer()
    165      */
    166     public boolean isNode() {
    167         return !isContainer();
    168     }
    169
    170     /***
    171      * If true, this node is axiliary and can only be used as an intermediate in
    172      * the chain of pointers.
    173      */
    174     public boolean isContainer() {
    175         return false;
    176     }
    177
    178     /***
    179      * If the pointer represents a collection, the index identifies
    180      * an element of that collection.  The default value of <code>index</code>
    181      * is <code>WHOLE_COLLECTION</code>, which just means that the pointer
    182      * is not indexed at all.
    183      * Note: the index on NodePointer starts with 0, not 1.
    184      */
    185     public int getIndex() {
    186         return index;
    187     }
    188
    189     public void setIndex(int index) {
    190         this.index = index;
    191     }
    192
    193     /***
    194      * Returns <code>true</code> if the value of the pointer is an array or
    195      * a Collection.
    196      */
    197     public abstract boolean isCollection();
    198
    199     /***
    200      * If the pointer represents a collection (or collection element),
    201      * returns the length of the collection.
    202      * Otherwise returns 1 (even if the value is null).
    203      */
    204     public abstract int getLength();
    205
    206     /***
    207      * By default, returns <code>getNode()</code>, can be overridden to
    208      * return a "canonical" value, like for instance a DOM element should
    209      * return its string value.
    210      */
    211     public Object getValue() {
    212         NodePointer valuePointer = getValuePointer();
    213         if (valuePointer != this) {
    214             return valuePointer.getValue();
    215         }
    216         // Default behavior is to return the same as getNode()
    217         return getNode();
    218     }
    219
    220     /***
    221      * If this pointer manages a transparent container, like a variable,
    222      * this method returns the pointer to the contents.
    223      * Only an auxiliary (non-node) pointer can (and should) return a
    224      * value pointer other than itself.
    225      * Note that you probably don't want to override
    226      * <code>getValuePointer()</code> directly.  Override the
    227      * <code>getImmediateValuePointer()</code> method instead.  The
    228      * <code>getValuePointer()</code> method is calls
    229      * <code>getImmediateValuePointer()</code> and, if the result is not
    230      * <code>this</code>, invokes <code>getValuePointer()</code> recursively.
    231      * The idea here is to open all nested containers. Let's say we have a
    232      * container within a container within a container. The
    233      * <code>getValuePointer()</code> method should then open all those
    234      * containers and return the pointer to the ultimate contents. It does so
    235      * with the above recursion.
    236      */
    237     public NodePointer getValuePointer() {
    238         NodePointer ivp = getImmediateValuePointer();
    239         if (ivp != this) {
    240             return ivp.getValuePointer();
    241         }
    242         return this;
    243     }
    244
    245     /***
    246      * @see #getValuePointer()
    247      *
    248      * @return NodePointer is either <code>this</code> or a pointer
    249      *   for the immediately contained value.
    250      */
    251     public NodePointer getImmediateValuePointer() {
    252         return this;
    253     }
    254
    255     /***
    256      * An actual pointer points to an existing part of an object graph, even
    257      * if it is null. A non-actual pointer represents a part that does not exist
    258      * at all.
    259      * For instance consider the pointer "/address/street".
    260      * If both <em>address</em> and <em>street</em> are not null,
    261      * the pointer is actual.
    262      * If <em>address</em> is not null, but <em>street</em> is null,
    263      * the pointer is still actual.
    264      * If <em>address</em> is null, the pointer is not actual.
    265      * (In JavaBeans) if <em>address</em> is not a property of the root bean,
    266      * a Pointer for this path cannot be obtained at all - actual or otherwise.
    267      */
    268     public boolean isActual() {
    269         if (index == WHOLE_COLLECTION) {
    270             return true;
    271         }
    272         else {
    273             return index >= 0 && index < getLength();
    274         }
    275     }
    276
    277     /***
    278      * Returns the name of this node. Can be null.
    279      */
    280     public abstract QName getName();
    281
    282     /***
    283      * Returns the value represented by the pointer before indexing.
    284      * So, if the node represents an element of a collection, this
    285      * method returns the collection itself.
    286      */
    287     public abstract Object getBaseValue();
    288
    289     /***
    290      * Returns the object the pointer points to; does not convert it
    291      * to a "canonical" type.
    292      *
    293      * @deprecated 1.1 Please use getNode()
    294      */
    295     public Object getNodeValue() {
    296         return getNode();
    297     }
    298
    299     /***
    300      * Returns the object the pointer points to; does not convert it
    301      * to a "canonical" type. Opens containers, properties etc and returns
    302      * the ultimate contents.
    303      */
    304     public Object getNode() {
    305         return getValuePointer().getImmediateNode();
    306     }
    307
    308     public Object getRootNode() {
    309         if (rootNode == null) {
    310             if (parent != null) {
    311                 rootNode = parent.getRootNode();
    312             }
    313             else {
    314                 rootNode = getImmediateNode();
    315             }
    316         }
    317         return rootNode;
    318     }
    319
    320     /***
    321      * Returns the object the pointer points to; does not convert it
    322      * to a "canonical" type.
    323      */
    324     public abstract Object getImmediateNode();
    325
    326     /***
    327      * Converts the value to the required type and changes the corresponding
    328      * object to that value.
    329      */
    330     public abstract void setValue(Object value);
    331
    332     /***
    333      * Compares two child NodePointers and returns a positive number,
    334      * zero or a positive number according to the order of the pointers.
    335      */
    336     public abstract int compareChildNodePointers(
    337             NodePointer pointer1, NodePointer pointer2);
    338
    339     /***
    340      * Checks if this Pointer matches the supplied NodeTest.
    341      */
    342     public boolean testNode(NodeTest test) {
    343         if (test == null) {
    344             return true;
    345         }
    346         else if (test instanceof NodeNameTest) {
    347             if (isContainer()) {
    348                 return false;
    349             }
    350             NodeNameTest nodeNameTest = (NodeNameTest) test;
    351             QName testName = nodeNameTest.getNodeName();
    352             QName nodeName = getName();
    353             if (nodeName == null) {
    354                 return false;
    355             }
    356
    357             String testPrefix = testName.getPrefix();
    358             String nodePrefix = nodeName.getPrefix();
    359             if (!equalStrings(testPrefix, nodePrefix)) {
    360                 String testNS = getNamespaceURI(testPrefix);
    361                 String nodeNS = getNamespaceURI(nodePrefix);
    362                 if (!equalStrings(testNS, nodeNS)) {
    363                     return false;
    364                 }
    365             }
    366             if (nodeNameTest.isWildcard()) {
    367                 return true;
    368             }
    369             return testName.getName().equals(nodeName.getName());
    370         }
    371         else if (test instanceof NodeTypeTest) {
    372             if (((NodeTypeTest) test).getNodeType()
    373                 == Compiler.NODE_TYPE_NODE) {
    374                 return isNode();
    375             }
    376         }
    377         return false;
    378     }
    379
    380     private static boolean equalStrings(String s1, String s2) {
    381         if (s1 == null && s2 != null) {
    382             return false;
    383         }
    384         if (s1 != null && !s1.equals(s2)) {
    385             return false;
    386         }
    387         return true;
    388     }
    389
    390     /***
    391      *  Called directly by JXPathContext. Must create path and
    392      *  set value.
    393      */
    394     public NodePointer createPath(JXPathContext context, Object value) {
    395         setValue(value);
    396         return this;
    397     }
    398
    399     /***
    400      * Remove the node of the object graph this pointer points to.
    401      */
    402     public void remove() {
    403         // It is a no-op
    404
    405 //        System.err.println("REMOVING: " + asPath() + " " + getClass());
    406 //        printPointerChain();
    407     }
    408
    409     /***
    410      * Called by a child pointer when it needs to create a parent object.
    411      * Must create an object described by this pointer and return
    412      * a new pointer that properly describes the new object.
    413      */
    414     public NodePointer createPath(JXPathContext context) {
    415         return this;
    416     }
    417
    418     /***
    419      * Called by a child pointer if that child needs to assign the value
    420      * supplied in the createPath(context, value) call to a non-existent
    421      * node. This method may have to expand the collection in order to assign
    422      * the element.
    423      */
    424     public NodePointer createChild(
    425         JXPathContext context,
    426         QName name,
    427         int index,
    428         Object value)
    429     {
    430         throw new JXPathException(
    431             "Cannot create an object for path "
    432                 + asPath()
    433                 + "/"
    434                 + name
    435                 + "["
    436                 + (index + 1)
    437                 + "]"
    438                 + ", operation is not allowed for this type of node");
    439     }
    440
    441     /***
    442      * Called by a child pointer when it needs to create a parent object
    443      * for a non-existent collection element.  It may have to expand the
    444      * collection, then create an element object and return a new pointer
    445      * describing the newly created element.
    446      */
    447     public NodePointer createChild(
    448         JXPathContext context,
    449         QName name,
    450         int index)
    451     {
    452         throw new JXPathException(
    453             "Cannot create an object for path "
    454                 + asPath()
    455                 + "/"
    456                 + name
    457                 + "["
    458                 + (index + 1)
    459                 + "]"
    460                 + ", operation is not allowed for this type of node");
    461     }
    462
    463     /***
    464      * Called to create a non-existing attribute
    465      */
    466     public NodePointer createAttribute(JXPathContext context, QName name) {
    467         throw new JXPathException(
    468             "Cannot create an attribute for path "
    469                 + asPath() + "/@" + name
    470                 + ", operation is not allowed for this type of node");
    471     }
    472
    473     /***
    474      * If the Pointer has a parent, returns the parent's locale;
    475      * otherwise returns the locale specified when this Pointer
    476      * was created.
    477      */
    478     public Locale getLocale() {
    479         if (locale == null) {
    480             if (parent != null) {
    481                 locale = parent.getLocale();
    482             }
    483         }
    484         return locale;
    485     }
    486
    487     /***
    488      * Returns true if the selected locale name starts
    489      * with the specified prefix <i>lang</i>, case-insensitive.
    490      */
    491     public boolean isLanguage(String lang) {
    492         Locale loc = getLocale();
    493         String name = loc.toString().replace('_', '-');
    494         return name.toUpperCase().startsWith(lang.toUpperCase());
    495     }
    496
    497 //    /***
    498 //     * Installs the supplied manager as the namespace manager for this node
    499 //     * pointer. The {@link #getNamespaceURI(String) getNamespaceURI(prefix)}
    500 //     * uses this manager to resolve namespace prefixes.
    501 //     *
    502 //     * @param namespaceManager
    503 //     */
    504 //    public void setNamespaceManager(NamespaceManager namespaceManager) {
    505 //        this.namespaceManager = namespaceManager;
    506 //    }
    507 //
    508 //    public NamespaceManager getNamespaceManager() {
    509 //        if (namespaceManager != null) {
    510 //            return namespaceManager;
    511 //        }
    512 //        if (parent != null) {
    513 //            return parent.getNamespaceManager();
    514 //        }
    515 //        return null;
    516 //    }
    517 //
    518     /***
    519      * Returns a NodeIterator that iterates over all children or all children
    520      * that match the given NodeTest, starting with the specified one.
    521      */
    522     public NodeIterator childIterator(
    523         NodeTest test,
    524         boolean reverse,
    525         NodePointer startWith)
    526     {
    527         NodePointer valuePointer = getValuePointer();
    528         if (valuePointer != null && valuePointer != this) {
    529             return valuePointer.childIterator(test, reverse, startWith);
    530         }
    531         return null;
    532     }
    533
    534     /***
    535      * Returns a NodeIterator that iterates over all attributes of the current
    536      * node matching the supplied node name (could have a wildcard).
    537      * May return null if the object does not support the attributes.
    538      */
    539     public NodeIterator attributeIterator(QName qname) {
    540         NodePointer valuePointer = getValuePointer();
    541         if (valuePointer != null && valuePointer != this) {
    542             return valuePointer.attributeIterator(qname);
    543         }
    544         return null;
    545     }
    546
    547     /***
    548      * Returns a NodeIterator that iterates over all namespaces of the value
    549      * currently pointed at.
    550      * May return null if the object does not support the namespaces.
    551      */
    552     public NodeIterator namespaceIterator() {
    553         return null;
    554     }
    555
    556     /***
    557      * Returns a NodePointer for the specified namespace. Will return null
    558      * if namespaces are not supported.
    559      * Will return UNKNOWN_NAMESPACE if there is no such namespace.
    560      */
    561     public NodePointer namespacePointer(String namespace) {
    562         return null;
    563     }
    564
    565     /***
    566      * Decodes a namespace prefix to the corresponding URI.
    567      */
    568     public String getNamespaceURI(String prefix) {
    569         return null;
    570     }
    571
    572     /***
    573      * Returns the namespace URI associated with this Pointer.
    574      */
    575     public String getNamespaceURI() {
    576         return null;
    577     }
    578
    579     /***
    580      * Returns true if the supplied prefix represents the
    581      * default namespace in the context of the current node.
    582      */
    583     protected boolean isDefaultNamespace(String prefix) {
    584         if (prefix == null) {
    585             return true;
    586         }
    587
    588         String namespace = getNamespaceURI(prefix);
    589         if (namespace == null) {
    590             return false; // undefined namespace
    591         }
    592
    593         return namespace.equals(getDefaultNamespaceURI());
    594     }
    595
    596     protected String getDefaultNamespaceURI() {
    597         return null;
    598     }
    599
    600     /***
    601      * Locates a node by ID.
    602      */
    603     public Pointer getPointerByID(JXPathContext context, String id) {
    604         return context.getPointerByID(id);
    605     }
    606
    607     /***
    608      * Locates a node by key and value.
    609      */
    610     public Pointer getPointerByKey(
    611         JXPathContext context,
    612         String key,
    613         String value)
    614     {
    615         return context.getPointerByKey(key, value);
    616     }
    617
    618     /***
    619      * Returns an XPath that maps to this Pointer.
    620      */
    621     public String asPath() {
    622         // If the parent of this node is a container, it is responsible
    623         // for appended this node's part of the path.
    624         if (parent != null && parent.isContainer()) {
    625             return parent.asPath();
    626         }
    627
    628         StringBuffer buffer = new StringBuffer();
    629         if (parent != null) {
    630             buffer.append(parent.asPath());
    631         }
    632
    633         if (buffer.length() == 0
    634             || buffer.charAt(buffer.length() - 1) != '/') {
    635             buffer.append('/');
    636         }
    637         if (attribute) {
    638             buffer.append('@');
    639         }
    640         buffer.append(getName());
    641
    642         if (index != WHOLE_COLLECTION && isCollection()) {
    643             buffer.append('[').append(index + 1).append(']');
    644         }
    645         return buffer.toString();
    646     }
    647
    648     public Object clone() {
    649         try {
    650             NodePointer ptr = (NodePointer) super.clone();
    651             if (parent != null) {
    652                 ptr.parent = (NodePointer) parent.clone();
    653             }
    654             return ptr;
    655         }
    656         catch (CloneNotSupportedException ex) {
    657             // Of course it is supported
    658             ex.printStackTrace();
    659         }
    660         return null;
    661     }
    662
    663     public String toString() {
    664         return asPath();
    665     }
    666
    667     public int compareTo(Object object) {
    668         // Let it throw a ClassCastException
    669         NodePointer pointer = (NodePointer) object;
    670         if (parent == pointer.parent) {
    671             if (parent == null) {
    672                 return 0;
    673             }
    674             return parent.compareChildNodePointers(this, pointer);
    675         }
    676
    677         // Task 1: find the common parent
    678         int depth1 = 0;
    679         NodePointer p1 = this;
    680         while (p1 != null) {
    681             depth1++;
    682             p1 = p1.parent;
    683         }
    684         int depth2 = 0;
    685         NodePointer p2 = pointer;
    686         while (p2 != null) {
    687             depth2++;
    688             p2 = p2.parent;
    689         }
    690         return compareNodePointers(this, depth1, pointer, depth2);
    691     }
    692
    693     private int compareNodePointers(
    694         NodePointer p1,
    695         int depth1,
    696         NodePointer p2,
    697         int depth2)
    698     {
    699         if (depth1 < depth2) {
    700             int r = compareNodePointers(p1, depth1, p2.parent, depth2 - 1);
    701             if (r != 0) {
    702                 return r;
    703             }
    704             return -1;
    705         }
    706         else if (depth1 > depth2) {
    707             int r = compareNodePointers(p1.parent, depth1 - 1, p2, depth2);
    708             if (r != 0) {
    709                 return r;
    710             }
    711             return 1;
    712         }
    713         if (p1 == null && p2 == null) {
    714             return 0;
    715         }
    716
    717         if (p1 != null && p1.equals(p2)) {
    718             return 0;
    719         }
    720
    721         if (depth1 == 1) {
    722             throw new JXPathException(
    723                 "Cannot compare pointers that do not belong to the same tree: '"
    724                     + p1
    725                     + "' and '"
    726                     + p2
    727                     + "'");
    728         }
    729         int r =
    730             compareNodePointers(p1.parent, depth1 - 1, p2.parent, depth2 - 1);
    731         if (r != 0) {
    732             return r;
    733         }
    734
    735         return p1.parent.compareChildNodePointers(p1, p2);
    736     }
    737
    738     /***
    739      * Print internal structure of a pointer for debugging
    740      */
    741     public void printPointerChain() {
    742         printDeep(this, "");
    743     }
    744
    745     private static void printDeep(NodePointer pointer, String indent) {
    746         if (indent.length() == 0) {
    747             System.err.println(
    748                 "POINTER: "
    749                     + pointer
    750                     + "("
    751                     + pointer.getClass().getName()
    752                     + ")");
    753         }
    754         else {
    755             System.err.println(
    756                 indent
    757                     + " of "
    758                     + pointer
    759                     + "("
    760                     + pointer.getClass().getName()
    761                     + ")");
    762         }
    763         if (pointer.getImmediateParentPointer() != null) {
    764             printDeep(pointer.getImmediateParentPointer(), indent + "  ");
    765         }
    766     }
    767 }