    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.jxpath.ri.model.jdom;
    018
    019    import java.util.List;
    020    import java.util.Locale;
    021
    022    import org.apache.commons.jxpath.JXPathAbstractFactoryException;
    023    import org.apache.commons.jxpath.JXPathContext;
    024    import org.apache.commons.jxpath.JXPathException;
    025    import org.apache.commons.jxpath.ri.Compiler;
    026    import org.apache.commons.jxpath.ri.NamespaceResolver;
    027    import org.apache.commons.jxpath.ri.QName;
    028    import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
    029    import org.apache.commons.jxpath.ri.compiler.NodeTest;
    030    import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
    031    import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
    032    import org.apache.commons.jxpath.ri.model.NodeIterator;
    033    import org.apache.commons.jxpath.ri.model.NodePointer;
    034    import org.apache.commons.jxpath.util.TypeUtils;
    035    import org.jdom.Attribute;
    036    import org.jdom.CDATA;
    037    import org.jdom.Comment;
    038    import org.jdom.Document;
    039    import org.jdom.Element;
    040    import org.jdom.Namespace;
    041    import org.jdom.ProcessingInstruction;
    042    import org.jdom.Text;
    043
    044    /**
    045     * A Pointer that points to a DOM node.
    046     *
    047     * @author Dmitri Plotnikov
    048     * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
    049     */
    050    public class JDOMNodePointer extends NodePointer {
    051        private static final long serialVersionUID = -6346532297491082651L;
    052
    053        private Object node;
    054        private String id;
    055        private NamespaceResolver localNamespaceResolver;
    056
    057        /** XML ns uri */
    058        public static final String XML_NAMESPACE_URI =
    059                "http://www.w3.org/XML/1998/namespace";
    060
    061        /** XMLNS ns uri */
    062        public static final String XMLNS_NAMESPACE_URI =
    063                "http://www.w3.org/2000/xmlns/";
    064
    065        /**
    066         * Create a new JDOMNodePointer.
    067         * @param node pointed
    068         * @param locale Locale
    069         */
    070        public JDOMNodePointer(Object node, Locale locale) {
    071            super(null, locale);
    072            this.node = node;
    073        }
    074
    075        /**
    076         * Create a new JDOMNodePointer.
    077         * @param node pointed
    078         * @param locale Locale
    079         * @param id String id
    080         */
    081        public JDOMNodePointer(Object node, Locale locale, String id) {
    082            super(null, locale);
    083            this.node = node;
    084            this.id = id;
    085        }
    086
    087        /**
    088         * Create a new JDOMNodePointer.
    089         * @param parent NodePointer
    090         * @param node pointed
    091         */
    092        public JDOMNodePointer(NodePointer parent, Object node) {
    093            super(parent);
    094            this.node = node;
    095        }
    096
    097        public NodeIterator childIterator(
    098            NodeTest test,
    099            boolean reverse,
    100            NodePointer startWith) {
    101            return new JDOMNodeIterator(this, test, reverse, startWith);
    102        }
    103
    104        public NodeIterator attributeIterator(QName name) {
    105            return new JDOMAttributeIterator(this, name);
    106        }
    107
    108        public NodeIterator namespaceIterator() {
    109            return new JDOMNamespaceIterator(this);
    110        }
    111
    112        public NodePointer namespacePointer(String prefix) {
    113            return new JDOMNamespacePointer(this, prefix);
    114        }
    115
    116        public String getNamespaceURI() {
    117            return getNamespaceURI(node);
    118        }
    119
    120        /**
    121         * Get the ns uri of the specified node.
    122         * @param node Node to check
    123         * @return String
    124         */
    125        private static String getNamespaceURI(Object node) {
    126            if (node instanceof Element) {
    127                Element element = (Element) node;
    128                String ns = element.getNamespaceURI();
    129                if (ns != null && ns.equals("")) {
    130                    ns = null;
    131                }
    132                return ns;
    133            }
    134            return null;
    135        }
    136
    137        public synchronized NamespaceResolver getNamespaceResolver() {
    138            if (localNamespaceResolver == null) {
    139                localNamespaceResolver = new NamespaceResolver(super.getNamespaceResolver());
    140                localNamespaceResolver.setNamespaceContextPointer(this);
    141            }
    142            return localNamespaceResolver;
    143        }
    144
    145        public String getNamespaceURI(String prefix) {
    146            if (prefix.equals("xml")) {
    147                return Namespace.XML_NAMESPACE.getURI();
    148            }
    149            Element element = null;
    150            if (node instanceof Document) {
    151                element = ((Document) node).getRootElement();
    152            }
    153            if (node instanceof Element) {
    154                element = (Element) node;
    155            }
    156            if (element == null) {
    157                return null;
    158            }
    159            Namespace ns = element.getNamespace(prefix);
    160            return ns == null ? null : ns.getURI();
    161        }
    162
    163        public int compareChildNodePointers(
    164            NodePointer pointer1,
    165            NodePointer pointer2) {
    166            Object node1 = pointer1.getBaseValue();
    167            Object node2 = pointer2.getBaseValue();
    168            if (node1 == node2) {
    169                return 0;
    170            }
    171
    172            if ((node1 instanceof Attribute) && !(node2 instanceof Attribute)) {
    173                return -1;
    174            }
    175            if (
    176                !(node1 instanceof Attribute) && (node2 instanceof Attribute)) {
    177                return 1;
    178            }
    179            if (
    180                (node1 instanceof Attribute) && (node2 instanceof Attribute)) {
    181                List list = ((Element) getNode()).getAttributes();
    182                int length = list.size();
    183                for (int i = 0; i < length; i++) {
    184                    Object n = list.get(i);
    185                    if (n == node1) {
    186                        return -1;
    187                    }
    188                    else if (n == node2) {
    189                        return 1;
    190                    }
    191                }
    192                return 0; // Should not happen
    193            }
    194
    195            if (!(node instanceof Element)) {
    196                throw new RuntimeException(
    197                    "JXPath internal error: "
    198                        + "compareChildNodes called for "
    199                        + node);
    200            }
    201
    202            List children = ((Element) node).getContent();
    203            int length = children.size();
    204            for (int i = 0; i < length; i++) {
    205                Object n = children.get(i);
    206                if (n == node1) {
    207                    return -1;
    208                }
    209                if (n == node2) {
    210                    return 1;
    211                }
    212            }
    213
    214            return 0;
    215        }
    216
    217        public Object getBaseValue() {
    218            return node;
    219        }
    220
    221        public boolean isCollection() {
    222            return false;
    223        }
    224
    225        public int getLength() {
    226            return 1;
    227        }
    228
    229        public boolean isLeaf() {
    230            if (node instanceof Element) {
    231                return ((Element) node).getContent().size() == 0;
    232            }
    233            if (node instanceof Document) {
    234                return ((Document) node).getContent().size() == 0;
    235            }
    236            return true;
    237        }
    238
    239        public QName getName() {
    240            String ns = null;
    241            String ln = null;
    242            if (node instanceof Element) {
    243                ns = ((Element) node).getNamespacePrefix();
    244                if (ns != null && ns.equals("")) {
    245                    ns = null;
    246                }
    247                ln = ((Element) node).getName();
    248            }
    249            else if (node instanceof ProcessingInstruction) {
    250                ln = ((ProcessingInstruction) node).getTarget();
    251            }
    252            return new QName(ns, ln);
    253        }
    254
    255        public Object getImmediateNode() {
    256            return node;
    257        }
    258
    259        public Object getValue() {
    260            if (node instanceof Element) {
    261                StringBuffer buf = new StringBuffer();
    262                for (NodeIterator children = childIterator(null, false, null); children.setPosition(children.getPosition() + 1);) {
    263                    NodePointer ptr = children.getNodePointer();
    264                    if (ptr.getImmediateNode() instanceof Element || ptr.getImmediateNode() instanceof Text) {
    265                        buf.append(ptr.getValue());
    266                    }
    267                }
    268                return buf.toString();
    269            }
    270            if (node instanceof Comment) {
    271                String text = ((Comment) node).getText();
    272                if (text != null) {
    273                    text = text.trim();
    274                }
    275                return text;
    276            }
    277            String result = null;
    278            if (node instanceof Text) {
    279                result = ((Text) node).getText();
    280            }
    281            if (node instanceof ProcessingInstruction) {
    282                result = ((ProcessingInstruction) node).getData();
    283            }
    284            boolean trim = !"preserve".equals(findEnclosingAttribute(node, "space", Namespace.XML_NAMESPACE));
    285            return result != null && trim ? result.trim() : result;
    286        }
    287
    288        public void setValue(Object value) {
    289            if (node instanceof Text) {
    290                String string = (String) TypeUtils.convert(value, String.class);
    291                if (string != null && !string.equals("")) {
    292                    ((Text) node).setText(string);
    293                }
    294                else {
    295                    nodeParent(node).removeContent((Text) node);
    296                }
    297            }
    298            else {
    299                Element element = (Element) node;
    300                element.getContent().clear();
    301
    302                if (value instanceof Element) {
    303                    Element valueElement = (Element) value;
    304                    addContent(valueElement.getContent());
    305                }
    306                else if (value instanceof Document) {
    307                    Document valueDocument = (Document) value;
    308                    addContent(valueDocument.getContent());
    309                }
    310                else if (value instanceof Text || value instanceof CDATA) {
    311                    String string = ((Text) value).getText();
    312                    element.addContent(new Text(string));
    313                }
    314                else if (value instanceof ProcessingInstruction) {
    315                    ProcessingInstruction pi =
    316                        (ProcessingInstruction) ((ProcessingInstruction) value)
    317                            .clone();
    318                    element.addContent(pi);
    319                }
    320                else if (value instanceof Comment) {
    321                    Comment comment = (Comment) ((Comment) value).clone();
    322                    element.addContent(comment);
    323                }
    324                else {
    325                    String string = (String) TypeUtils.convert(value, String.class);
    326                    if (string != null && !string.equals("")) {
    327                        element.addContent(new Text(string));
    328                    }
    329                }
    330            }
    331        }
    332
    333        /**
    334         * Add the specified content to this element.
    335         * @param content List
    336         */
    337        private void addContent(List content) {
    338            Element element = (Element) node;
    339            int count = content.size();
    340
    341            for (int i = 0; i < count; i++) {
    342                Object child = content.get(i);
    343                if (child instanceof Element) {
    344                    child = ((Element) child).clone();
    345                    element.addContent((Element) child);
    346                }
    347                else if (child instanceof Text) {
    348                    child = ((Text) child).clone();
    349                    element.addContent((Text) child);
    350                }
    351                else if (node instanceof CDATA) {
    352                    child = ((CDATA) child).clone();
    353                    element.addContent((CDATA) child);
    354                }
    355                else if (node instanceof ProcessingInstruction) {
    356                    child = ((ProcessingInstruction) child).clone();
    357                    element.addContent((ProcessingInstruction) child);
    358                }
    359                else if (node instanceof Comment) {
    360                    child = ((Comment) child).clone();
    361                    element.addContent((Comment) child);
    362                }
    363            }
    364        }
    365
    366        public boolean testNode(NodeTest test) {
    367            return testNode(this, node, test);
    368        }
    369
    370        /**
    371         * Execute test against node on behalf of pointer.
    372         * @param pointer Pointer
    373         * @param node to test
    374         * @param test to execute
    375         * @return true if node passes test
    376         */
    377        public static boolean testNode(
    378            NodePointer pointer,
    379            Object node,
    380            NodeTest test) {
    381            if (test == null) {
    382                return true;
    383            }
    384            if (test instanceof NodeNameTest) {
    385                if (!(node instanceof Element)) {
    386                    return false;
    387                }
    388
    389                NodeNameTest nodeNameTest = (NodeNameTest) test;
    390                QName testName = nodeNameTest.getNodeName();
    391                String namespaceURI = nodeNameTest.getNamespaceURI();
    392                boolean wildcard = nodeNameTest.isWildcard();
    393                String testPrefix = testName.getPrefix();
    394                if (wildcard && testPrefix == null) {
    395                    return true;
    396                }
    397                if (wildcard
    398                    || testName.getName()
    399                            .equals(JDOMNodePointer.getLocalName(node))) {
    400                    String nodeNS = JDOMNodePointer.getNamespaceURI(node);
    401                    return equalStrings(namespaceURI, nodeNS) || nodeNS == null
    402                            && equalStrings(testPrefix, getPrefix(node));
    403                }
    404                return false;
    405            }
    406            if (test instanceof NodeTypeTest) {
    407                switch (((NodeTypeTest) test).getNodeType()) {
    408                    case Compiler.NODE_TYPE_NODE :
    409                        return true;
    410                    case Compiler.NODE_TYPE_TEXT :
    411                        return (node instanceof Text) || (node instanceof CDATA);
    412                    case Compiler.NODE_TYPE_COMMENT :
    413                        return node instanceof Comment;
    414                    case Compiler.NODE_TYPE_PI :
    415                        return node instanceof ProcessingInstruction;
    416                    default:
    417                        return false;
    418                }
    419            }
    420            if (test instanceof ProcessingInstructionTest && node instanceof ProcessingInstruction) {
    421                String testPI = ((ProcessingInstructionTest) test).getTarget();
    422                String nodePI = ((ProcessingInstruction) node).getTarget();
    423                return testPI.equals(nodePI);
    424            }
    425            return false;
    426        }
    427
    428        /**
    429         * Learn whether two strings are == or .equals()
    430         * @param s1 string 1
    431         * @param s2 string 2
    432         * @return true if equal
    433         */
    434        private static boolean equalStrings(String s1, String s2) {
    435            if (s1 == s2) {
    436                return true;
    437            }
    438            s1 = s1 == null ? "" : s1.trim();
    439            s2 = s2 == null ? "" : s2.trim();
    440            return s1.equals(s2);
    441        }
    442
    443        /**
    444         * Get the prefix from a given node.
    445         * @param node to check
    446         * @return String
    447         */
    448        public static String getPrefix(Object node) {
    449            if (node instanceof Element) {
    450                String prefix = ((Element) node).getNamespacePrefix();
    451                return (prefix == null || prefix.equals("")) ? null : prefix;
    452            }
    453            if (node instanceof Attribute) {
    454                String prefix = ((Attribute) node).getNamespacePrefix();
    455                return (prefix == null || prefix.equals("")) ? null : prefix;
    456            }
    457            return null;
    458        }
    459
    460        /**
    461         * Get the local name of the specified node.
    462         * @param node to check
    463         * @return String local name
    464         */
    465        public static String getLocalName(Object node) {
    466            if (node instanceof Element) {
    467                return ((Element) node).getName();
    468            }
    469            if (node instanceof Attribute) {
    470                return ((Attribute) node).getName();
    471            }
    472            return null;
    473        }
    474
    475        /**
    476         * Returns true if the xml:lang attribute for the current node
    477         * or its parent has the specified prefix <i>lang</i>.
    478         * If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
    479         * @param lang to compare
    480         * @return true if this element uses the specified language.
    481         */
    482        public boolean isLanguage(String lang) {
    483            String current = getLanguage();
    484            return current == null ? super.isLanguage(lang) : current.toUpperCase(
    485                    Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
    486        }
    487
    488        /**
    489         * Get the language of this element.
    490         * @return String language
    491         */
    492        protected String getLanguage() {
    493            return findEnclosingAttribute(node, "lang", Namespace.XML_NAMESPACE);
    494        }
    495
    496        /**
    497         * Find the nearest occurrence of the specified attribute
    498         * on the specified and enclosing elements.
    499         * @param n current node
    500         * @param attrName attribute name
    501         * @param ns Namespace
    502         * @return attribute value
    503         */
    504        protected static String findEnclosingAttribute(Object n, String attrName, Namespace ns) {
    505            while (n != null) {
    506                if (n instanceof Element) {
    507                    Element e = (Element) n;
    508                    String attr = e.getAttributeValue(attrName, ns);
    509                    if (attr != null && !attr.equals("")) {
    510                        return attr;
    511                    }
    512                }
    513                n = nodeParent(n);
    514            }
    515            return null;
    516        }
    517
    518        /**
    519         * Get the parent of the specified node.
    520         * @param node to check
    521         * @return parent Element
    522         */
    523        private static Element nodeParent(Object node) {
    524            if (node instanceof Element) {
    525                Object parent = ((Element) node).getParent();
    526                return parent instanceof Element ? (Element) parent : null;
    527            }
    528            if (node instanceof Text) {
    529                return (Element) ((Text) node).getParent();
    530            }
    531            if (node instanceof CDATA) {
    532                return (Element) ((CDATA) node).getParent();
    533            }
    534            if (node instanceof ProcessingInstruction) {
    535                return (Element) ((ProcessingInstruction) node).getParent();
    536            }
    537            if (node instanceof Comment) {
    538                return (Element) ((Comment) node).getParent();
    539            }
    540            return null;
    541        }
    542
    543        public NodePointer createChild(
    544            JXPathContext context,
    545            QName name,
    546            int index) {
    547            if (index == WHOLE_COLLECTION) {
    548                index = 0;
    549            }
    550            boolean success =
    551                getAbstractFactory(context).createObject(
    552                    context,
    553                    this,
    554                    node,
    555                    name.toString(),
    556                    index);
    557            if (success) {
    558                NodeTest nodeTest;
    559                String prefix = name.getPrefix();
    560                String namespaceURI = prefix == null ? null : context
    561                        .getNamespaceURI(prefix);
    562                nodeTest = new NodeNameTest(name, namespaceURI);
    563
    564                NodeIterator it =
    565                    childIterator(nodeTest, false, null);
    566                if (it != null && it.setPosition(index + 1)) {
    567                    return it.getNodePointer();
    568                }
    569            }
    570            throw new JXPathAbstractFactoryException("Factory could not create "
    571                    + "a child node for path: " + asPath() + "/" + name + "["
    572                    + (index + 1) + "]");
    573        }
    574
    575        public NodePointer createChild(
    576                JXPathContext context, QName name, int index, Object value) {
    577            NodePointer ptr = createChild(context, name, index);
    578            ptr.setValue(value);
    579            return ptr;
    580        }
    581
    582        public NodePointer createAttribute(JXPathContext context, QName name) {
    583            if (!(node instanceof Element)) {
    584                return super.createAttribute(context, name);
    585            }
    586
    587            Element element = (Element) node;
    588            String prefix = name.getPrefix();
    589            if (prefix != null) {
    590                String namespaceUri = getNamespaceResolver().getNamespaceURI(prefix);
    591                if (namespaceUri == null) {
    592                    throw new JXPathException(
    593                        "Unknown namespace prefix: " + prefix);
    594                }
    595                Namespace ns = Namespace.getNamespace(prefix, namespaceUri);
    596                Attribute attr = element.getAttribute(name.getName(), ns);
    597                if (attr == null) {
    598                    element.setAttribute(name.getName(), "", ns);
    599                }
    600            }
    601            else {
    602                Attribute attr = element.getAttribute(name.getName());
    603                if (attr == null) {
    604                    element.setAttribute(name.getName(), "");
    605                }
    606            }
    607            NodeIterator it = attributeIterator(name);
    608            it.setPosition(1);
    609            return it.getNodePointer();
    610        }
    611
    612        public void remove() {
    613            Element parent = nodeParent(node);
    614            if (parent == null) {
    615                throw new JXPathException("Cannot remove root JDOM node");
    616            }
    617            parent.getContent().remove(node);
    618        }
    619
    620        public String asPath() {
    621            if (id != null) {
    622                return "id('" + escape(id) + "')";
    623            }
    624
    625            StringBuffer buffer = new StringBuffer();
    626            if (parent != null) {
    627                buffer.append(parent.asPath());
    628            }
    629            if (node instanceof Element) {
    630                // If the parent pointer is not a JDOMNodePointer, it is
    631                // the parent's responsibility to produce the node test part
    632                // of the path
    633                if (parent instanceof JDOMNodePointer) {
    634                    if (buffer.length() == 0
    635                        || buffer.charAt(buffer.length() - 1) != '/') {
    636                        buffer.append('/');
    637                    }
    638                    String nsURI = getNamespaceURI();
    639                    String ln = JDOMNodePointer.getLocalName(node);
    640
    641                    if (nsURI == null) {
    642                        buffer.append(ln);
    643                        buffer.append('[');
    644                        buffer.append(getRelativePositionByName()).append(']');
    645                    }
    646                    else {
    647                        String prefix = getNamespaceResolver().getPrefix(nsURI);
    648                        if (prefix != null) {
    649                            buffer.append(prefix);
    650                            buffer.append(':');
    651                            buffer.append(ln);
    652                            buffer.append('[');
    653                            buffer.append(getRelativePositionByName());
    654                            buffer.append(']');
    655                        }
    656                        else {
    657                            buffer.append("node()");
    658                            buffer.append('[');
    659                            buffer.append(getRelativePositionOfElement());
    660                            buffer.append(']');
    661                        }
    662                    }
    663
    664                }
    665            }
    666            else if (node instanceof Text || node instanceof CDATA) {
    667                buffer.append("/text()");
    668                buffer.append('[').append(getRelativePositionOfTextNode()).append(
    669                    ']');
    670            }
    671            else if (node instanceof ProcessingInstruction) {
    672                buffer.append("/processing-instruction(\'").append(((ProcessingInstruction) node).getTarget()).append(
    673                    "')");
    674                buffer.append('[').append(getRelativePositionOfPI()).append(
    675                    ']');
    676            }
    677            return buffer.toString();
    678        }
    679
    680        /**
    681         * Get relative position of this among like-named siblings.
    682         * @return 1..n
    683         */
    684        private int getRelativePositionByName() {
    685            if (node instanceof Element) {
    686                Object parent = ((Element) node).getParent();
    687                if (!(parent instanceof Element)) {
    688                    return 1;
    689                }
    690
    691                List children = ((Element) parent).getContent();
    692                int count = 0;
    693                String name = ((Element) node).getQualifiedName();
    694                for (int i = 0; i < children.size(); i++) {
    695                    Object child = children.get(i);
    696                    if ((child instanceof Element)
    697                        && ((Element) child).getQualifiedName().equals(name)) {
    698                        count++;
    699                    }
    700                    if (child == node) {
    701                        break;
    702                    }
    703                }
    704                return count;
    705            }
    706            return 1;
    707        }
    708
    709        /**
    710         * Get relative position of this among all siblings.
    711         * @return 1..n
    712         */
    713        private int getRelativePositionOfElement() {
    714            Object parent = ((Element) node).getParent();
    715            if (parent == null) {
    716                return 1;
    717            }
    718            List children;
    719            if (parent instanceof Element) {
    720                children = ((Element) parent).getContent();
    721            }
    722            else {
    723                children = ((Document) parent).getContent();
    724            }
    725            int count = 0;
    726            for (int i = 0; i < children.size(); i++) {
    727                Object child = children.get(i);
    728                if (child instanceof Element) {
    729                    count++;
    730                }
    731                if (child == node) {
    732                    break;
    733                }
    734            }
    735            return count;
    736        }
    737
    738        /**
    739         * Get the relative position of this among sibling text nodes.
    740         * @return 1..n
    741         */
    742        private int getRelativePositionOfTextNode() {
    743            Element parent;
    744            if (node instanceof Text) {
    745                parent = (Element) ((Text) node).getParent();
    746            }
    747            else {
    748                parent = (Element) ((CDATA) node).getParent();
    749            }
    750            if (parent == null) {
    751                return 1;
    752            }
    753            List children = parent.getContent();
    754            int count = 0;
    755            for (int i = 0; i < children.size(); i++) {
    756                Object child = children.get(i);
    757                if (child instanceof Text || child instanceof CDATA) {
    758                    count++;
    759                }
    760                if (child == node) {
    761                    break;
    762                }
    763            }
    764            return count;
    765        }
    766
    767        /**
    768         * Get the relative position of this among same-target processing instruction siblings.
    769         * @return 1..n
    770         */
    771        private int getRelativePositionOfPI() {
    772            String target = ((ProcessingInstruction) node).getTarget();
    773            Element parent = (Element) ((ProcessingInstruction) node).getParent();
    774            if (parent == null) {
    775                return 1;
    776            }
    777            List children = parent.getContent();
    778            int count = 0;
    779            for (int i = 0; i < children.size(); i++) {
    780                Object child = children.get(i);
    781                if (child instanceof ProcessingInstruction
    782                    && (target == null
    783                        || target.equals(
    784                            ((ProcessingInstruction) child).getTarget()))) {
    785                    count++;
    786                }
    787                if (child == node) {
    788                    break;
    789                }
    790            }
    791            return count;
    792        }
    793
    794        public int hashCode() {
    795            return node.hashCode();
    796        }
    797
    798        public boolean equals(Object object) {
    799            if (object == this) {
    800                return true;
    801            }
    802
    803            if (!(object instanceof JDOMNodePointer)) {
    804                return false;
    805            }
    806
    807            JDOMNodePointer other = (JDOMNodePointer) object;
    808            return node == other.node;
    809        }
    810
    811    }