View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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) {
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 }