001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code 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     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
238     */
239    @Override
240    public AxisIterator iterateAxis(int axisNumber) {
241        final AxisIterator result;
242        switch (axisNumber) {
243            case AxisInfo.ANCESTOR:
244                result = new Navigator.AncestorEnumeration(this, false);
245                break;
246            case AxisInfo.ANCESTOR_OR_SELF:
247                result = new Navigator.AncestorEnumeration(this, true);
248                break;
249            case AxisInfo.ATTRIBUTE:
250                result = SingleNodeIterator.makeIterator(getAttributeNode());
251                break;
252            case AxisInfo.CHILD:
253                if (hasChildNodes()) {
254                    result = new ArrayIterator.OfNodes(
255                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
256                }
257                else {
258                    result = EmptyIterator.ofNodes();
259                }
260                break;
261            case AxisInfo.DESCENDANT:
262                if (hasChildNodes()) {
263                    result = new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN);
264                }
265                else {
266                    result = EmptyIterator.ofNodes();
267                }
268                break;
269            case AxisInfo.DESCENDANT_OR_SELF:
270                result = new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE);
271                break;
272            case AxisInfo.PARENT:
273                result = SingleNodeIterator.makeIterator(parent);
274                break;
275            case AxisInfo.SELF:
276                result = SingleNodeIterator.makeIterator(this);
277                break;
278            case AxisInfo.FOLLOWING_SIBLING:
279                result = getFollowingSiblingsIterator();
280                break;
281            case AxisInfo.PRECEDING_SIBLING:
282                result = getPrecedingSiblingsIterator();
283                break;
284            case AxisInfo.FOLLOWING:
285                result = new FollowingIterator(this);
286                break;
287            case AxisInfo.PRECEDING:
288                result = new PrecedingIterator(this);
289                break;
290            default:
291                throw throwUnsupportedOperationException();
292        }
293
294        return result;
295    }
296
297    /**
298     * Returns line number.
299     *
300     * @return line number
301     */
302    @Override
303    public int getLineNumber() {
304        return detailAst.getLineNo();
305    }
306
307    /**
308     * Returns column number.
309     *
310     * @return column number
311     */
312    @Override
313    public int getColumnNumber() {
314        return detailAst.getColumnNo();
315    }
316
317    /**
318     * Getter method for token type.
319     *
320     * @return token type
321     */
322    @Override
323    public int getTokenType() {
324        return detailAst.getType();
325    }
326
327    /**
328     * Returns underlying node.
329     *
330     * @return underlying node
331     */
332    @Override
333    public DetailAST getUnderlyingNode() {
334        return detailAst;
335    }
336
337    /**
338     * Returns preceding sibling axis iterator.
339     *
340     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
341     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
342     * but none of the subclasses of the {@link AxisIterator}
343     * class has non-empty {@code close()} method.
344     *
345     * @return iterator
346     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
347     */
348    private AxisIterator getPrecedingSiblingsIterator() {
349        final AxisIterator result;
350        if (indexAmongSiblings == 0) {
351            result = EmptyIterator.ofNodes();
352        }
353        else {
354            result = new ReverseListIterator(getPrecedingSiblings());
355        }
356        return result;
357    }
358
359    /**
360     * Returns following sibling axis iterator.
361     *
362     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
363     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
364     * but none of the subclasses of the {@link AxisIterator}
365     * class has non-empty {@code close()} method.
366     *
367     * @return iterator
368     * @noinspection resource, IOResourceOpenedButNotSafelyClosed
369     */
370    private AxisIterator getFollowingSiblingsIterator() {
371        final AxisIterator result;
372        if (indexAmongSiblings == parent.getChildren().size() - 1) {
373            result = EmptyIterator.ofNodes();
374        }
375        else {
376            result = new ArrayIterator.OfNodes(
377                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
378        }
379        return result;
380    }
381
382    /**
383     * Returns following siblings of the current node.
384     *
385     * @return siblings
386     */
387    private List<AbstractNode> getFollowingSiblings() {
388        final List<AbstractNode> siblings = parent.getChildren();
389        return siblings.subList(indexAmongSiblings + 1, siblings.size());
390    }
391
392    /**
393     * Returns preceding siblings of the current node.
394     *
395     * @return siblings
396     */
397    private List<AbstractNode> getPrecedingSiblings() {
398        final List<AbstractNode> siblings = parent.getChildren();
399        return siblings.subList(0, indexAmongSiblings);
400    }
401
402    /**
403     * Checks if token type supports {@code @text} attribute,
404     * extracts its value, creates {@code AttributeNode} object and returns it.
405     * Value can be accessed using {@code @text} attribute.
406     *
407     * @return attribute node if possible, otherwise the {@code null} value
408     */
409    private AttributeNode getAttributeNode() {
410        if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) {
411            if (XpathUtil.supportsTextAttribute(detailAst)) {
412                attributeNode = new AttributeNode(TEXT_ATTRIBUTE_NAME,
413                        XpathUtil.getTextAttributeValue(detailAst));
414            }
415            else {
416                attributeNode = null;
417            }
418        }
419        return attributeNode;
420    }
421
422    /**
423     * Returns UnsupportedOperationException exception.
424     *
425     * @return UnsupportedOperationException exception
426     */
427    private static UnsupportedOperationException throwUnsupportedOperationException() {
428        return new UnsupportedOperationException("Operation is not supported");
429    }
430}