001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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;
023import java.util.Optional;
024
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
027import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
028import com.puppycrawl.tools.checkstyle.xpath.iterators.DescendantIterator;
029import com.puppycrawl.tools.checkstyle.xpath.iterators.FollowingIterator;
030import com.puppycrawl.tools.checkstyle.xpath.iterators.PrecedingIterator;
031import com.puppycrawl.tools.checkstyle.xpath.iterators.ReverseListIterator;
032import net.sf.saxon.om.AxisInfo;
033import net.sf.saxon.om.NodeInfo;
034import net.sf.saxon.tree.iter.ArrayIterator;
035import net.sf.saxon.tree.iter.AxisIterator;
036import net.sf.saxon.tree.iter.EmptyIterator;
037import net.sf.saxon.tree.iter.SingleNodeIterator;
038import net.sf.saxon.tree.util.Navigator;
039import net.sf.saxon.type.Type;
040
041/**
042 * Represents element node of Xpath-tree.
043 *
044 */
045public class ElementNode extends AbstractNode {
046
047    /** String literal for text attribute. */
048    private static final String TEXT_ATTRIBUTE_NAME = "text";
049
050    /** Constant for optimization. */
051    private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0];
052
053    /** Holder value for lazy creation of attribute node. */
054    private static final AttributeNode ATTRIBUTE_NODE_UNINITIALIZED = new AttributeNode(null, null);
055
056    /** The root node. */
057    private final AbstractNode root;
058
059    /** The parent of the current node. */
060    private final AbstractNode parent;
061
062    /** The ast node. */
063    private final DetailAST detailAst;
064
065    /** Depth of the node. */
066    private final int depth;
067
068    /** Represents index among siblings. */
069    private final int indexAmongSiblings;
070
071    /** The text attribute node. */
072    private AttributeNode attributeNode = ATTRIBUTE_NODE_UNINITIALIZED;
073
074    /**
075     * Creates a new {@code ElementNode} instance.
076     *
077     * @param root {@code Node} root of the tree
078     * @param parent {@code Node} parent of the current node
079     * @param detailAst reference to {@code DetailAST}
080     * @param depth the current node depth in the hierarchy
081     * @param indexAmongSiblings the current node index among the parent children nodes
082     */
083    public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst,
084            int depth, int indexAmongSiblings) {
085        super(root.getTreeInfo());
086        this.parent = parent;
087        this.root = root;
088        this.detailAst = detailAst;
089        this.depth = depth;
090        this.indexAmongSiblings = indexAmongSiblings;
091    }
092
093    /**
094     * Compares current object with specified for order.
095     *
096     * @param other another {@code NodeInfo} object
097     * @return number representing order of current object to specified one
098     */
099    @Override
100    public int compareOrder(NodeInfo other) {
101        int result = 0;
102        if (other instanceof AbstractNode) {
103            result = Integer.compare(depth, ((AbstractNode) other).getDepth());
104            if (result == 0) {
105                result = compareCommonAncestorChildrenOrder(this, other);
106            }
107        }
108        return result;
109    }
110
111    /**
112     * Walks up the hierarchy until a common ancestor is found.
113     * Then compares topmost sibling nodes.
114     *
115     * @param first {@code NodeInfo} to compare
116     * @param second {@code NodeInfo} to compare
117     * @return the value {@code 0} if {@code first == second};
118     *         a value less than {@code 0} if {@code first} should be first;
119     *         a value greater than {@code 0} if {@code second} should be first.
120     */
121    private static int compareCommonAncestorChildrenOrder(NodeInfo first, NodeInfo second) {
122        NodeInfo child1 = first;
123        NodeInfo child2 = second;
124        while (!child1.getParent().equals(child2.getParent())) {
125            child1 = child1.getParent();
126            child2 = child2.getParent();
127        }
128        final int index1 = ((ElementNode) child1).indexAmongSiblings;
129        final int index2 = ((ElementNode) child2).indexAmongSiblings;
130        return Integer.compare(index1, index2);
131    }
132
133    /**
134     * Getter method for node depth.
135     *
136     * @return depth
137     */
138    @Override
139    public int getDepth() {
140        return depth;
141    }
142
143    /**
144     * Iterates children of the current node and
145     * recursively creates new Xpath-nodes.
146     *
147     * @return children list
148     */
149    @Override
150    protected List<AbstractNode> createChildren() {
151        return XpathUtil.createChildren(root, this, detailAst.getFirstChild());
152    }
153
154    /**
155     * Determine whether the node has any children.
156     *
157     * @return {@code true} is the node has any children.
158     */
159    @Override
160    public boolean hasChildNodes() {
161        return detailAst.hasChildren();
162    }
163
164    /**
165     * Returns attribute value. Throws {@code UnsupportedOperationException} in case,
166     * when name of the attribute is not equal to 'text'.
167     *
168     * @param namespace namespace
169     * @param localPart actual name of the attribute
170     * @return attribute value
171     */
172    @Override
173    public String getAttributeValue(String namespace, String localPart) {
174        final String result;
175        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
176            result = Optional.ofNullable(getAttributeNode())
177                .map(AttributeNode::getStringValue)
178                .orElse(null);
179        }
180        else {
181            result = null;
182        }
183        return result;
184    }
185
186    /**
187     * Returns local part.
188     *
189     * @return local part
190     */
191    @Override
192    public String getLocalPart() {
193        return TokenUtil.getTokenName(detailAst.getType());
194    }
195
196    /**
197     * Returns type of the node.
198     *
199     * @return node kind
200     */
201    @Override
202    public int getNodeKind() {
203        return Type.ELEMENT;
204    }
205
206    /**
207     * Returns parent.
208     *
209     * @return parent
210     */
211    @Override
212    public NodeInfo getParent() {
213        return parent;
214    }
215
216    /**
217     * Returns root.
218     *
219     * @return root
220     */
221    @Override
222    public NodeInfo getRoot() {
223        return root;
224    }
225
226    /**
227     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
228     * when there is no axis iterator for given axisNumber.
229     *
230     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
231     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
232     * but none of the subclasses of the {@link AxisIterator}
233     * class has non-empty {@code close()} method.
234     *
235     * @param axisNumber element from {@code AxisInfo}
236     * @return {@code AxisIterator} object
237     */
238    @Override
239    public AxisIterator iterateAxis(int axisNumber) {
240        final AxisIterator result;
241        switch (axisNumber) {
242            case AxisInfo.ANCESTOR:
243                result = new Navigator.AncestorEnumeration(this, false);
244                break;
245            case AxisInfo.ANCESTOR_OR_SELF:
246                result = new Navigator.AncestorEnumeration(this, true);
247                break;
248            case AxisInfo.ATTRIBUTE:
249                result = SingleNodeIterator.makeIterator(getAttributeNode());
250                break;
251            case AxisInfo.CHILD:
252                if (hasChildNodes()) {
253                    result = new ArrayIterator.OfNodes<>(
254                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
255                }
256                else {
257                    result = EmptyIterator.ofNodes();
258                }
259                break;
260            case AxisInfo.DESCENDANT:
261                if (hasChildNodes()) {
262                    result = new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN);
263                }
264                else {
265                    result = EmptyIterator.ofNodes();
266                }
267                break;
268            case AxisInfo.DESCENDANT_OR_SELF:
269                result = new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE);
270                break;
271            case AxisInfo.PARENT:
272                result = SingleNodeIterator.makeIterator(parent);
273                break;
274            case AxisInfo.SELF:
275                result = SingleNodeIterator.makeIterator(this);
276                break;
277            case AxisInfo.FOLLOWING_SIBLING:
278                result = getFollowingSiblingsIterator();
279                break;
280            case AxisInfo.PRECEDING_SIBLING:
281                result = getPrecedingSiblingsIterator();
282                break;
283            case AxisInfo.FOLLOWING:
284                result = new FollowingIterator(this);
285                break;
286            case AxisInfo.PRECEDING:
287                result = new PrecedingIterator(this);
288                break;
289            default:
290                throw throwUnsupportedOperationException();
291        }
292
293        return result;
294    }
295
296    /**
297     * Returns line number.
298     *
299     * @return line number
300     */
301    @Override
302    public int getLineNumber() {
303        return detailAst.getLineNo();
304    }
305
306    /**
307     * Returns column number.
308     *
309     * @return column number
310     */
311    @Override
312    public int getColumnNumber() {
313        return detailAst.getColumnNo();
314    }
315
316    /**
317     * Getter method for token type.
318     *
319     * @return token type
320     */
321    @Override
322    public int getTokenType() {
323        return detailAst.getType();
324    }
325
326    /**
327     * Returns underlying node.
328     *
329     * @return underlying node
330     */
331    @Override
332    public DetailAST getUnderlyingNode() {
333        return detailAst;
334    }
335
336    /**
337     * Returns preceding sibling axis iterator.
338     *
339     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
340     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
341     * but none of the subclasses of the {@link AxisIterator}
342     * class has non-empty {@code close()} method.
343     *
344     * @return iterator
345     */
346    private AxisIterator getPrecedingSiblingsIterator() {
347        final AxisIterator result;
348        if (indexAmongSiblings == 0) {
349            result = EmptyIterator.ofNodes();
350        }
351        else {
352            result = new ReverseListIterator(getPrecedingSiblings());
353        }
354        return result;
355    }
356
357    /**
358     * Returns following sibling axis iterator.
359     *
360     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
361     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
362     * but none of the subclasses of the {@link AxisIterator}
363     * class has non-empty {@code close()} method.
364     *
365     * @return iterator
366     */
367    private AxisIterator getFollowingSiblingsIterator() {
368        final AxisIterator result;
369        if (indexAmongSiblings == parent.getChildren().size() - 1) {
370            result = EmptyIterator.ofNodes();
371        }
372        else {
373            result = new ArrayIterator.OfNodes<>(
374                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
375        }
376        return result;
377    }
378
379    /**
380     * Returns following siblings of the current node.
381     *
382     * @return siblings
383     */
384    private List<AbstractNode> getFollowingSiblings() {
385        final List<AbstractNode> siblings = parent.getChildren();
386        return siblings.subList(indexAmongSiblings + 1, siblings.size());
387    }
388
389    /**
390     * Returns preceding siblings of the current node.
391     *
392     * @return siblings
393     */
394    private List<AbstractNode> getPrecedingSiblings() {
395        final List<AbstractNode> siblings = parent.getChildren();
396        return siblings.subList(0, indexAmongSiblings);
397    }
398
399    /**
400     * Checks if token type supports {@code @text} attribute,
401     * extracts its value, creates {@code AttributeNode} object and returns it.
402     * Value can be accessed using {@code @text} attribute.
403     *
404     * @return attribute node if possible, otherwise the {@code null} value
405     */
406    private AttributeNode getAttributeNode() {
407        if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) {
408            if (XpathUtil.supportsTextAttribute(detailAst)) {
409                attributeNode = new AttributeNode(TEXT_ATTRIBUTE_NAME,
410                        XpathUtil.getTextAttributeValue(detailAst));
411            }
412            else {
413                attributeNode = null;
414            }
415        }
416        return attributeNode;
417    }
418
419    /**
420     * Returns UnsupportedOperationException exception.
421     *
422     * @return UnsupportedOperationException exception
423     */
424    private static UnsupportedOperationException throwUnsupportedOperationException() {
425        return new UnsupportedOperationException("Operation is not supported");
426    }
427}