001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.xpath;
021
022import java.util.List;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
026import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
027import net.sf.saxon.om.AxisInfo;
028import net.sf.saxon.om.NodeInfo;
029import net.sf.saxon.tree.iter.ArrayIterator;
030import net.sf.saxon.tree.iter.AxisIterator;
031import net.sf.saxon.tree.iter.EmptyIterator;
032import net.sf.saxon.tree.iter.SingleNodeIterator;
033import net.sf.saxon.tree.util.Navigator;
034import net.sf.saxon.type.Type;
035
036/**
037 * Represents element node of Xpath-tree.
038 *
039 */
040public class ElementNode extends AbstractNode {
041
042    /** String literal for text attribute. */
043    private static final String TEXT_ATTRIBUTE_NAME = "text";
044
045    /** Constant for optimization. */
046    private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0];
047
048    /** The root node. */
049    private final AbstractNode root;
050
051    /** The parent of the current node. */
052    private final AbstractNode parent;
053
054    /** The ast node. */
055    private final DetailAST detailAst;
056
057    /** Represents text of the DetailAST. */
058    private final String text;
059
060    /** Represents index among siblings. */
061    private final int indexAmongSiblings;
062
063    /** The text attribute node. */
064    private AttributeNode attributeNode;
065
066    /**
067     * Creates a new {@code ElementNode} instance.
068     *
069     * @param root {@code Node} root of the tree
070     * @param parent {@code Node} parent of the current node
071     * @param detailAst reference to {@code DetailAST}
072     */
073    public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst) {
074        super(root.getTreeInfo());
075        this.parent = parent;
076        this.root = root;
077        this.detailAst = detailAst;
078        text = TokenUtil.getTokenName(detailAst.getType());
079        indexAmongSiblings = parent.getChildren().size();
080        setDepth(parent.getDepth() + 1);
081        createTextAttribute();
082        createChildren();
083    }
084
085    /**
086     * Compares current object with specified for order.
087     *
088     * @param other another {@code NodeInfo} object
089     * @return number representing order of current object to specified one
090     */
091    @Override
092    public int compareOrder(NodeInfo other) {
093        int result = 0;
094        if (other instanceof AbstractNode) {
095            result = getDepth() - ((AbstractNode) other).getDepth();
096            if (result == 0) {
097                final ElementNode[] children = getCommonAncestorChildren(other);
098                result = children[0].indexAmongSiblings - children[1].indexAmongSiblings;
099            }
100        }
101        return result;
102    }
103
104    /**
105     * Finds the ancestors of the children whose parent is their common ancestor.
106     *
107     * @param other another {@code NodeInfo} object
108     * @return {@code ElementNode} immediate children(also ancestors of the given children) of the
109     *         common ancestor
110     */
111    private ElementNode[] getCommonAncestorChildren(NodeInfo other) {
112        NodeInfo child1 = this;
113        NodeInfo child2 = other;
114        while (!child1.getParent().equals(child2.getParent())) {
115            child1 = child1.getParent();
116            child2 = child2.getParent();
117        }
118        return new ElementNode[] {(ElementNode) child1, (ElementNode) child2};
119    }
120
121    /**
122     * Iterates children of the current node and
123     * recursively creates new Xpath-nodes.
124     */
125    private void createChildren() {
126        DetailAST currentChild = detailAst.getFirstChild();
127        while (currentChild != null) {
128            final AbstractNode child = new ElementNode(root, this, currentChild);
129            addChild(child);
130            currentChild = currentChild.getNextSibling();
131        }
132    }
133
134    /**
135     * Returns attribute value. Throws {@code UnsupportedOperationException} in case,
136     * when name of the attribute is not equal to 'text'.
137     *
138     * @param namespace namespace
139     * @param localPart actual name of the attribute
140     * @return attribute value
141     */
142    @Override
143    public String getAttributeValue(String namespace, String localPart) {
144        final String result;
145        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
146            if (attributeNode == null) {
147                result = null;
148            }
149            else {
150                result = attributeNode.getStringValue();
151            }
152        }
153        else {
154            result = null;
155        }
156        return result;
157    }
158
159    /**
160     * Returns local part.
161     *
162     * @return local part
163     */
164    @Override
165    public String getLocalPart() {
166        return text;
167    }
168
169    /**
170     * Returns type of the node.
171     *
172     * @return node kind
173     */
174    @Override
175    public int getNodeKind() {
176        return Type.ELEMENT;
177    }
178
179    /**
180     * Returns parent.
181     *
182     * @return parent
183     */
184    @Override
185    public NodeInfo getParent() {
186        return parent;
187    }
188
189    /**
190     * Returns root.
191     *
192     * @return root
193     */
194    @Override
195    public NodeInfo getRoot() {
196        return root;
197    }
198
199    /**
200     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
201     * when there is no axis iterator for given axisNumber.
202     *
203     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
204     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
205     * but none of the subclasses of the {@link AxisIterator}
206     * class has non-empty {@code close()} method.
207     *
208     * @param axisNumber element from {@code AxisInfo}
209     * @return {@code AxisIterator} object
210     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
211     */
212    @Override
213    public AxisIterator iterateAxis(int axisNumber) {
214        final AxisIterator result;
215        switch (axisNumber) {
216            case AxisInfo.ANCESTOR:
217                result = new Navigator.AncestorEnumeration(this, false);
218                break;
219            case AxisInfo.ANCESTOR_OR_SELF:
220                result = new Navigator.AncestorEnumeration(this, true);
221                break;
222            case AxisInfo.ATTRIBUTE:
223                result = SingleNodeIterator.makeIterator(attributeNode);
224                break;
225            case AxisInfo.CHILD:
226                if (hasChildNodes()) {
227                    result = new ArrayIterator.OfNodes(
228                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
229                }
230                else {
231                    result = EmptyIterator.ofNodes();
232                }
233                break;
234            case AxisInfo.DESCENDANT:
235                if (hasChildNodes()) {
236                    result = new Navigator.DescendantEnumeration(this, false, true);
237                }
238                else {
239                    result = EmptyIterator.ofNodes();
240                }
241                break;
242            case AxisInfo.DESCENDANT_OR_SELF:
243                result = new Navigator.DescendantEnumeration(this, true, true);
244                break;
245            case AxisInfo.PARENT:
246                result = SingleNodeIterator.makeIterator(parent);
247                break;
248            case AxisInfo.SELF:
249                result = SingleNodeIterator.makeIterator(this);
250                break;
251            case AxisInfo.FOLLOWING_SIBLING:
252                result = getFollowingSiblingsIterator();
253                break;
254            case AxisInfo.PRECEDING_SIBLING:
255                result = getPrecedingSiblingsIterator();
256                break;
257            case AxisInfo.FOLLOWING:
258                result = new FollowingEnumeration(this);
259                break;
260            case AxisInfo.PRECEDING:
261                result = new Navigator.PrecedingEnumeration(this, true);
262                break;
263            default:
264                throw throwUnsupportedOperationException();
265        }
266
267        return result;
268    }
269
270    /**
271     * Returns line number.
272     *
273     * @return line number
274     */
275    @Override
276    public int getLineNumber() {
277        return detailAst.getLineNo();
278    }
279
280    /**
281     * Returns column number.
282     *
283     * @return column number
284     */
285    @Override
286    public int getColumnNumber() {
287        return detailAst.getColumnNo();
288    }
289
290    /**
291     * Getter method for token type.
292     *
293     * @return token type
294     */
295    @Override
296    public int getTokenType() {
297        return detailAst.getType();
298    }
299
300    /**
301     * Returns underlying node.
302     *
303     * @return underlying node
304     */
305    @Override
306    public DetailAST getUnderlyingNode() {
307        return detailAst;
308    }
309
310    /**
311     * Returns preceding sibling axis iterator.
312     *
313     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
314     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
315     * but none of the subclasses of the {@link AxisIterator}
316     * class has non-empty {@code close()} method.
317     *
318     * @return iterator
319     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
320     */
321    private AxisIterator getPrecedingSiblingsIterator() {
322        final AxisIterator result;
323        if (indexAmongSiblings == 0) {
324            result = EmptyIterator.ofNodes();
325        }
326        else {
327            result = new ArrayIterator.OfNodes(
328                    getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
329        }
330        return result;
331    }
332
333    /**
334     * Returns following sibling axis iterator.
335     *
336     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
337     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
338     * but none of the subclasses of the {@link AxisIterator}
339     * class has non-empty {@code close()} method.
340     *
341     * @return iterator
342     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
343     */
344    private AxisIterator getFollowingSiblingsIterator() {
345        final AxisIterator result;
346        if (indexAmongSiblings == parent.getChildren().size() - 1) {
347            result = EmptyIterator.ofNodes();
348        }
349        else {
350            result = new ArrayIterator.OfNodes(
351                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
352        }
353        return result;
354    }
355
356    /**
357     * Returns following siblings of the current node.
358     *
359     * @return siblings
360     */
361    private List<AbstractNode> getFollowingSiblings() {
362        final List<AbstractNode> siblings = parent.getChildren();
363        return siblings.subList(indexAmongSiblings + 1, siblings.size());
364    }
365
366    /**
367     * Returns preceding siblings of the current node.
368     *
369     * @return siblings
370     */
371    private List<AbstractNode> getPrecedingSiblings() {
372        final List<AbstractNode> siblings = parent.getChildren();
373        return siblings.subList(0, indexAmongSiblings);
374    }
375
376    /**
377     * Checks if token type supports {@code @text} attribute,
378     * extracts its value, creates {@code AttributeNode} object and returns it.
379     * Value can be accessed using {@code @text} attribute.
380     */
381    private void createTextAttribute() {
382        AttributeNode attribute = null;
383        if (XpathUtil.supportsTextAttribute(detailAst)) {
384            attribute = new AttributeNode(TEXT_ATTRIBUTE_NAME,
385                    XpathUtil.getTextAttributeValue(detailAst));
386        }
387        attributeNode = attribute;
388    }
389
390    /**
391     * Returns UnsupportedOperationException exception.
392     *
393     * @return UnsupportedOperationException exception
394     */
395    private static UnsupportedOperationException throwUnsupportedOperationException() {
396        return new UnsupportedOperationException("Operation is not supported");
397    }
398
399    /**
400     * Implementation of the following axis, in terms of the child and following-sibling axes.
401     */
402    private static final class FollowingEnumeration implements AxisIterator {
403        /** Following-sibling axis iterator. */
404        private AxisIterator siblingEnum;
405        /** Child axis iterator. */
406        private AxisIterator descendEnum;
407
408        /**
409         * Create an iterator over the "following" axis.
410         *
411         * @param start the initial context node.
412         */
413        /* package */ FollowingEnumeration(NodeInfo start) {
414            siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING);
415        }
416
417        /**
418         * Get the next item in the sequence.
419         *
420         * @return the next Item. If there are no more nodes, return null.
421         */
422        @Override
423        public NodeInfo next() {
424            NodeInfo result = null;
425            if (descendEnum != null) {
426                result = descendEnum.next();
427            }
428
429            if (result == null) {
430                descendEnum = null;
431                result = siblingEnum.next();
432                if (result == null) {
433                    siblingEnum = null;
434                }
435                else {
436                    descendEnum = new Navigator.DescendantEnumeration(result, true, false);
437                    result = next();
438                }
439            }
440            return result;
441        }
442    }
443
444}