001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.GNode;
031import net.sf.saxon.om.NamespaceUri;
032import net.sf.saxon.om.NodeInfo;
033import net.sf.saxon.om.SequenceIterator;
034import net.sf.saxon.pattern.nodetest.NodePredicate;
035import net.sf.saxon.tree.iter.ArrayIterator;
036import net.sf.saxon.tree.iter.EmptyIterator;
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 GNode} object
097     * @return number representing order of current object to specified one
098     */
099    @Override
100    public int compareOrder(GNode other) {
101        int result = 0;
102        if (other instanceof final AbstractNode node) {
103            result = Integer.compare(depth, node.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 GNode} to compare
116     * @param second {@code GNode} to compare
117     * @return the value {@code 0} if {@code first == second}; a value less than
118     *         {@code 0} if {@code first} should be first; a value greater than
119     *         {@code 0} if {@code second} should be first.
120     */
121    private static int compareCommonAncestorChildrenOrder(GNode first, GNode second) {
122        GNode child1 = first;
123        GNode 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     * Returns attribute axis iterator.
196     *
197     * @param predicate filter for the iterator, may be null
198     * @return iterator over the {@code @text} attribute node, or an empty iterator
199     */
200    @Override
201    public SequenceIterator iterateAttributeAxis(NodePredicate predicate) {
202        return Navigator.filteredSingleton(getAttributeNode(), predicate);
203    }
204
205    /**
206     * Returns child axis iterator.
207     *
208     * @param predicate filter for the iterator, may be null
209     * @return iterator over the children
210     */
211    @Override
212    public SequenceIterator iterateChildAxis(NodePredicate predicate) {
213        final SequenceIterator result;
214        if (hasChildNodes()) {
215            result = filter(new ArrayIterator.OfNodes<>(
216                    getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY)), predicate);
217        }
218        else {
219            result = EmptyIterator.getInstance();
220        }
221        return result;
222    }
223
224    /**
225     * Returns descendant axis iterator.
226     *
227     * @param predicate filter for the iterator, may be null
228     * @return iterator over the descendants
229     */
230    @Override
231    public SequenceIterator iterateDescendantAxis(NodePredicate predicate) {
232        final SequenceIterator result;
233        if (hasChildNodes()) {
234            result = filter(
235                new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN), predicate);
236        }
237        else {
238            result = EmptyIterator.getInstance();
239        }
240        return result;
241    }
242
243    /**
244     * Returns descendant-or-self axis iterator.
245     *
246     * @param predicate filter for the iterator, may be null
247     * @return iterator over the current node and its descendants
248     */
249    @Override
250    public SequenceIterator iterateDescendantOrSelfAxis(NodePredicate predicate) {
251        return filter(
252            new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE), predicate);
253    }
254
255    /**
256     * Returns following-sibling axis iterator.
257     *
258     * @param predicate filter for the iterator, may be null
259     * @return iterator over the following siblings
260     */
261    @Override
262    public SequenceIterator iterateFollowingSiblingAxis(NodePredicate predicate) {
263        return filter(getFollowingSiblingsIterator(), predicate);
264    }
265
266    /**
267     * Returns preceding-sibling axis iterator.
268     *
269     * @param predicate filter for the iterator, may be null
270     * @return iterator over the preceding siblings
271     */
272    @Override
273    public SequenceIterator iteratePrecedingSiblingAxis(NodePredicate predicate) {
274        return filter(getPrecedingSiblingsIterator(), predicate);
275    }
276
277    /**
278     * Returns following axis iterator.
279     *
280     * @param predicate filter for the iterator, may be null
281     * @return iterator over the following nodes
282     */
283    @Override
284    public SequenceIterator iterateFollowingAxis(NodePredicate predicate) {
285        return filter(new FollowingIterator(this), predicate);
286    }
287
288    /**
289     * Returns preceding axis iterator.
290     *
291     * @param predicate filter for the iterator, may be null
292     * @return iterator over the preceding nodes
293     */
294    @Override
295    public SequenceIterator iteratePrecedingAxis(NodePredicate predicate) {
296        return filter(new PrecedingIterator(this), predicate);
297    }
298
299    /**
300     * Returns preceding sibling axis iterator.
301     *
302     * @return iterator
303     */
304    private SequenceIterator getPrecedingSiblingsIterator() {
305        final SequenceIterator result;
306        if (indexAmongSiblings == 0) {
307            result = EmptyIterator.getInstance();
308        }
309        else {
310            result = new ReverseListIterator(getPrecedingSiblings());
311        }
312        return result;
313    }
314
315    /**
316     * Returns following sibling axis iterator.
317     *
318     * @return iterator
319     */
320    private SequenceIterator getFollowingSiblingsIterator() {
321        final SequenceIterator result;
322        if (indexAmongSiblings == parent.getChildren().size() - 1) {
323            result = EmptyIterator.getInstance();
324        }
325        else {
326            result = new ArrayIterator.OfNodes<>(
327                    getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
328        }
329        return result;
330    }
331
332    /**
333     * Returns following siblings of the current node.
334     *
335     * @return siblings
336     */
337    private List<AbstractNode> getFollowingSiblings() {
338        final List<AbstractNode> siblings = parent.getChildren();
339        return siblings.subList(indexAmongSiblings + 1, siblings.size());
340    }
341
342    /**
343     * Returns preceding siblings of the current node.
344     *
345     * @return siblings
346     */
347    private List<AbstractNode> getPrecedingSiblings() {
348        final List<AbstractNode> siblings = parent.getChildren();
349        return Collections.unmodifiableList(siblings.subList(0, indexAmongSiblings));
350    }
351
352    /**
353     * Checks if token type supports {@code @text} attribute,
354     * extracts its value, creates {@code AttributeNode} object and returns it.
355     * Value can be accessed using {@code @text} attribute.
356     *
357     * @return attribute node if possible, otherwise the {@code null} value
358     */
359    @SuppressWarnings("ReferenceEquality")
360    private AttributeNode getAttributeNode() {
361        if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) {
362            attributeNode = createAttributeNode();
363        }
364        return attributeNode;
365    }
366
367    /**
368     * Namespace axis is not supported for element nodes.
369     *
370     * @param predicate filter for the iterator, may be null
371     * @return never
372     */
373    @Override
374    public SequenceIterator iterateNamespaceAxis(NodePredicate predicate) {
375        throw new UnsupportedOperationException("Operation is not supported");
376    }
377
378}