001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 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}