View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.xpath;
21  
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.xpath.iterators.DescendantIterator;
27  import com.puppycrawl.tools.checkstyle.xpath.iterators.FollowingIterator;
28  import com.puppycrawl.tools.checkstyle.xpath.iterators.PrecedingIterator;
29  import com.puppycrawl.tools.checkstyle.xpath.iterators.ReverseListIterator;
30  import net.sf.saxon.om.AxisInfo;
31  import net.sf.saxon.om.NamespaceUri;
32  import net.sf.saxon.om.NodeInfo;
33  import net.sf.saxon.tree.iter.ArrayIterator;
34  import net.sf.saxon.tree.iter.AxisIterator;
35  import net.sf.saxon.tree.iter.EmptyIterator;
36  import net.sf.saxon.tree.iter.SingleNodeIterator;
37  import net.sf.saxon.tree.util.Navigator;
38  import net.sf.saxon.type.Type;
39  
40  /**
41   * Represents element node of Xpath-tree.
42   */
43  public abstract class AbstractElementNode extends AbstractNode {
44  
45      /** String literal for text attribute. */
46      protected static final String TEXT_ATTRIBUTE_NAME = "text";
47  
48      /** Constant for optimization. */
49      private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0];
50  
51      /** Holder value for lazy creation of attribute node. */
52      private static final AttributeNode ATTRIBUTE_NODE_UNINITIALIZED = new AttributeNode(null, null);
53  
54      /** The root node. */
55      private final AbstractNode root;
56  
57      /** The parent of the current node. */
58      private final AbstractNode parent;
59  
60      /** Depth of the node. */
61      private final int depth;
62  
63      /** Represents index among siblings. */
64      private final int indexAmongSiblings;
65  
66      /** The text attribute node. */
67      private AttributeNode attributeNode = ATTRIBUTE_NODE_UNINITIALIZED;
68  
69      /**
70       * Creates a new {@code AbstractElementNode} instance.
71       *
72       * @param root {@code Node} root of the tree
73       * @param parent {@code Node} parent of the current node
74       * @param depth the current node depth in the hierarchy
75       * @param indexAmongSiblings the current node index among the parent children nodes
76       */
77      protected AbstractElementNode(AbstractNode root, AbstractNode parent,
78              int depth, int indexAmongSiblings) {
79          super(root.getTreeInfo());
80          this.parent = parent;
81          this.root = root;
82          this.depth = depth;
83          this.indexAmongSiblings = indexAmongSiblings;
84      }
85  
86      /**
87       * Creates {@code AttributeNode} object and returns it.
88       *
89       * @return attribute node if possible, otherwise the {@code null} value
90       */
91      protected abstract AttributeNode createAttributeNode();
92  
93      /**
94       * Compares current object with specified for order.
95       *
96       * @param other another {@code NodeInfo} object
97       * @return number representing order of current object to specified one
98       */
99      @Override
100     public int compareOrder(NodeInfo other) {
101         int result = 0;
102         if (other instanceof 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 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         return switch (axisNumber) {
209             case AxisInfo.ANCESTOR -> new Navigator.AncestorEnumeration(this, false);
210             case AxisInfo.ANCESTOR_OR_SELF -> new Navigator.AncestorEnumeration(this, true);
211             case AxisInfo.ATTRIBUTE -> SingleNodeIterator.makeIterator(getAttributeNode());
212             case AxisInfo.CHILD -> {
213                 if (hasChildNodes()) {
214                     yield new ArrayIterator.OfNodes<>(
215                             getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
216                 }
217                 yield EmptyIterator.ofNodes();
218             }
219             case AxisInfo.DESCENDANT -> {
220                 if (hasChildNodes()) {
221                     yield new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN);
222                 }
223                 yield EmptyIterator.ofNodes();
224             }
225             case AxisInfo.DESCENDANT_OR_SELF ->
226                 new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE);
227             case AxisInfo.PARENT -> SingleNodeIterator.makeIterator(parent);
228             case AxisInfo.SELF -> SingleNodeIterator.makeIterator(this);
229             case AxisInfo.FOLLOWING_SIBLING -> getFollowingSiblingsIterator();
230             case AxisInfo.PRECEDING_SIBLING -> getPrecedingSiblingsIterator();
231             case AxisInfo.FOLLOWING -> new FollowingIterator(this);
232             case AxisInfo.PRECEDING -> new PrecedingIterator(this);
233             default -> throw throwUnsupportedOperationException();
234         };
235     }
236 
237     /**
238      * Returns preceding sibling axis iterator.
239      *
240      * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
241      * {@link AxisIterator} implements {@link java.io.Closeable} interface,
242      * but none of the subclasses of the {@link AxisIterator}
243      * class has non-empty {@code close()} method.
244      *
245      * @return iterator
246      */
247     private AxisIterator getPrecedingSiblingsIterator() {
248         final AxisIterator result;
249         if (indexAmongSiblings == 0) {
250             result = EmptyIterator.ofNodes();
251         }
252         else {
253             result = new ReverseListIterator(getPrecedingSiblings());
254         }
255         return result;
256     }
257 
258     /**
259      * Returns following sibling axis iterator.
260      *
261      * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed:
262      * {@link AxisIterator} implements {@link java.io.Closeable} interface,
263      * but none of the subclasses of the {@link AxisIterator}
264      * class has non-empty {@code close()} method.
265      *
266      * @return iterator
267      */
268     private AxisIterator getFollowingSiblingsIterator() {
269         final AxisIterator result;
270         if (indexAmongSiblings == parent.getChildren().size() - 1) {
271             result = EmptyIterator.ofNodes();
272         }
273         else {
274             result = new ArrayIterator.OfNodes<>(
275                     getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY));
276         }
277         return result;
278     }
279 
280     /**
281      * Returns following siblings of the current node.
282      *
283      * @return siblings
284      */
285     private List<AbstractNode> getFollowingSiblings() {
286         final List<AbstractNode> siblings = parent.getChildren();
287         return siblings.subList(indexAmongSiblings + 1, siblings.size());
288     }
289 
290     /**
291      * Returns preceding siblings of the current node.
292      *
293      * @return siblings
294      */
295     private List<AbstractNode> getPrecedingSiblings() {
296         final List<AbstractNode> siblings = parent.getChildren();
297         return Collections.unmodifiableList(siblings.subList(0, indexAmongSiblings));
298     }
299 
300     /**
301      * Checks if token type supports {@code @text} attribute,
302      * extracts its value, creates {@code AttributeNode} object and returns it.
303      * Value can be accessed using {@code @text} attribute.
304      *
305      * @return attribute node if possible, otherwise the {@code null} value
306      */
307     private AttributeNode getAttributeNode() {
308         if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) {
309             attributeNode = createAttributeNode();
310         }
311         return attributeNode;
312     }
313 
314     /**
315      * Returns UnsupportedOperationException exception.
316      *
317      * @return UnsupportedOperationException exception
318      */
319     private static UnsupportedOperationException throwUnsupportedOperationException() {
320         return new UnsupportedOperationException("Operation is not supported");
321     }
322 }