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