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}