001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.Collections;
023import java.util.List;
024import java.util.Optional;
025
026import com.puppycrawl.tools.checkstyle.xpath.iterators.DescendantIterator;
027import com.puppycrawl.tools.checkstyle.xpath.iterators.FollowingIterator;
028import com.puppycrawl.tools.checkstyle.xpath.iterators.PrecedingIterator;
029import com.puppycrawl.tools.checkstyle.xpath.iterators.ReverseListIterator;
030import net.sf.saxon.om.AxisInfo;
031import net.sf.saxon.om.NamespaceUri;
032import net.sf.saxon.om.NodeInfo;
033import net.sf.saxon.tree.iter.ArrayIterator;
034import net.sf.saxon.tree.iter.AxisIterator;
035import net.sf.saxon.tree.iter.EmptyIterator;
036import net.sf.saxon.tree.iter.SingleNodeIterator;
037import net.sf.saxon.tree.util.Navigator;
038import net.sf.saxon.type.Type;
039
040/**
041 * Represents element node of Xpath-tree.
042 */
043public abstract class AbstractElementNode extends AbstractNode {
044
045    /** String literal for text attribute. */
046    protected static final String TEXT_ATTRIBUTE_NAME = "text";
047
048    /** Constant for optimization. */
049    private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0];
050
051    /** Holder value for lazy creation of attribute node. */
052    private static final AttributeNode ATTRIBUTE_NODE_UNINITIALIZED = new AttributeNode(null, null);
053
054    /** The root node. */
055    private final AbstractNode root;
056
057    /** The parent of the current node. */
058    private final AbstractNode parent;
059
060    /** Depth of the node. */
061    private final int depth;
062
063    /** Represents index among siblings. */
064    private final int indexAmongSiblings;
065
066    /** The text attribute node. */
067    private AttributeNode attributeNode = ATTRIBUTE_NODE_UNINITIALIZED;
068
069    /**
070     * Creates a new {@code AbstractElementNode} instance.
071     *
072     * @param root {@code Node} root of the tree
073     * @param parent {@code Node} parent of the current node
074     * @param depth the current node depth in the hierarchy
075     * @param indexAmongSiblings the current node index among the parent children nodes
076     */
077    protected AbstractElementNode(AbstractNode root, AbstractNode parent,
078            int depth, int indexAmongSiblings) {
079        super(root.getTreeInfo());
080        this.parent = parent;
081        this.root = root;
082        this.depth = depth;
083        this.indexAmongSiblings = indexAmongSiblings;
084    }
085
086    /**
087     * Creates {@code AttributeNode} object and returns it.
088     *
089     * @return attribute node if possible, otherwise the {@code null} value
090     */
091    protected abstract AttributeNode createAttributeNode();
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 = ((AbstractElementNode) child1).indexAmongSiblings;
129        final int index2 = ((AbstractElementNode) 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     * Returns attribute value.
145     *
146     * @param namespace namespace
147     * @param localPart actual name of the attribute
148     * @return attribute value or null if the attribute was not found
149     */
150    @Override
151    public String getAttributeValue(NamespaceUri namespace, String localPart) {
152        final String result;
153        if (TEXT_ATTRIBUTE_NAME.equals(localPart)) {
154            result = Optional.ofNullable(getAttributeNode())
155                .map(AttributeNode::getStringValue)
156                .orElse(null);
157        }
158        else {
159            result = null;
160        }
161        return result;
162    }
163
164    /**
165     * Returns type of the node.
166     *
167     * @return node kind
168     */
169    @Override
170    public int getNodeKind() {
171        return Type.ELEMENT;
172    }
173
174    /**
175     * Returns parent.
176     *
177     * @return parent
178     */
179    @Override
180    public NodeInfo getParent() {
181        return parent;
182    }
183
184    /**
185     * Returns root.
186     *
187     * @return root
188     */
189    @Override
190    public AbstractNode getRoot() {
191        return root;
192    }
193
194    /**
195     * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case,
196     * when there is no axis iterator for given axisNumber.
197     *
198     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
199     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
200     * but none of the subclasses of the {@link AxisIterator}
201     * class has non-empty {@code close()} method.
202     *
203     * @param axisNumber element from {@code AxisInfo}
204     * @return {@code AxisIterator} object
205     */
206    @Override
207    public AxisIterator iterateAxis(int axisNumber) {
208        final AxisIterator result;
209        switch (axisNumber) {
210            case AxisInfo.ANCESTOR:
211                result = new Navigator.AncestorEnumeration(this, false);
212                break;
213            case AxisInfo.ANCESTOR_OR_SELF:
214                result = new Navigator.AncestorEnumeration(this, true);
215                break;
216            case AxisInfo.ATTRIBUTE:
217                result = SingleNodeIterator.makeIterator(getAttributeNode());
218                break;
219            case AxisInfo.CHILD:
220                if (hasChildNodes()) {
221                    result = new ArrayIterator.OfNodes<>(
222                            getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
223                }
224                else {
225                    result = EmptyIterator.ofNodes();
226                }
227                break;
228            case AxisInfo.DESCENDANT:
229                if (hasChildNodes()) {
230                    result = new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN);
231                }
232                else {
233                    result = EmptyIterator.ofNodes();
234                }
235                break;
236            case AxisInfo.DESCENDANT_OR_SELF:
237                result = new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE);
238                break;
239            case AxisInfo.PARENT:
240                result = SingleNodeIterator.makeIterator(parent);
241                break;
242            case AxisInfo.SELF:
243                result = SingleNodeIterator.makeIterator(this);
244                break;
245            case AxisInfo.FOLLOWING_SIBLING:
246                result = getFollowingSiblingsIterator();
247                break;
248            case AxisInfo.PRECEDING_SIBLING:
249                result = getPrecedingSiblingsIterator();
250                break;
251            case AxisInfo.FOLLOWING:
252                result = new FollowingIterator(this);
253                break;
254            case AxisInfo.PRECEDING:
255                result = new PrecedingIterator(this);
256                break;
257            default:
258                throw throwUnsupportedOperationException();
259        }
260
261        return result;
262    }
263
264    /**
265     * Returns preceding sibling axis iterator.
266     *
267     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
268     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
269     * but none of the subclasses of the {@link AxisIterator}
270     * class has non-empty {@code close()} method.
271     *
272     * @return iterator
273     */
274    private AxisIterator getPrecedingSiblingsIterator() {
275        final AxisIterator result;
276        if (indexAmongSiblings == 0) {
277            result = EmptyIterator.ofNodes();
278        }
279        else {
280            result = new ReverseListIterator(getPrecedingSiblings());
281        }
282        return result;
283    }
284
285    /**
286     * Returns following sibling axis iterator.
287     *
288     * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
289     * {@link AxisIterator} implements {@link java.io.Closeable} interface,
290     * but none of the subclasses of the {@link AxisIterator}
291     * class has non-empty {@code close()} method.
292     *
293     * @return iterator
294     */
295    private AxisIterator getFollowingSiblingsIterator() {
296        final AxisIterator result;
297        if (indexAmongSiblings == parent.getChildren().size() - 1) {
298            result = EmptyIterator.ofNodes();
299        }
300        else {
301            result = new ArrayIterator.OfNodes<>(
302                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
303        }
304        return result;
305    }
306
307    /**
308     * Returns following siblings of the current node.
309     *
310     * @return siblings
311     */
312    private List<AbstractNode> getFollowingSiblings() {
313        final List<AbstractNode> siblings = parent.getChildren();
314        return siblings.subList(indexAmongSiblings + 1, siblings.size());
315    }
316
317    /**
318     * Returns preceding siblings of the current node.
319     *
320     * @return siblings
321     */
322    private List<AbstractNode> getPrecedingSiblings() {
323        final List<AbstractNode> siblings = parent.getChildren();
324        return Collections.unmodifiableList(siblings.subList(0, indexAmongSiblings));
325    }
326
327    /**
328     * Checks if token type supports {@code @text} attribute,
329     * extracts its value, creates {@code AttributeNode} object and returns it.
330     * Value can be accessed using {@code @text} attribute.
331     *
332     * @return attribute node if possible, otherwise the {@code null} value
333     */
334    private AttributeNode getAttributeNode() {
335        if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) {
336            attributeNode = createAttributeNode();
337        }
338        return attributeNode;
339    }
340
341    /**
342     * Returns UnsupportedOperationException exception.
343     *
344     * @return UnsupportedOperationException exception
345     */
346    private static UnsupportedOperationException throwUnsupportedOperationException() {
347        return new UnsupportedOperationException("Operation is not supported");
348    }
349}