001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.List; 023import java.util.Optional; 024 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 027import com.puppycrawl.tools.checkstyle.utils.XpathUtil; 028import com.puppycrawl.tools.checkstyle.xpath.iterators.DescendantIterator; 029import com.puppycrawl.tools.checkstyle.xpath.iterators.FollowingIterator; 030import com.puppycrawl.tools.checkstyle.xpath.iterators.PrecedingIterator; 031import com.puppycrawl.tools.checkstyle.xpath.iterators.ReverseListIterator; 032import net.sf.saxon.om.AxisInfo; 033import net.sf.saxon.om.NodeInfo; 034import net.sf.saxon.tree.iter.ArrayIterator; 035import net.sf.saxon.tree.iter.AxisIterator; 036import net.sf.saxon.tree.iter.EmptyIterator; 037import net.sf.saxon.tree.iter.SingleNodeIterator; 038import net.sf.saxon.tree.util.Navigator; 039import net.sf.saxon.type.Type; 040 041/** 042 * Represents element node of Xpath-tree. 043 * 044 */ 045public class ElementNode extends AbstractNode { 046 047 /** String literal for text attribute. */ 048 private static final String TEXT_ATTRIBUTE_NAME = "text"; 049 050 /** Constant for optimization. */ 051 private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0]; 052 053 /** Holder value for lazy creation of attribute node. */ 054 private static final AttributeNode ATTRIBUTE_NODE_UNINITIALIZED = new AttributeNode(null, null); 055 056 /** The root node. */ 057 private final AbstractNode root; 058 059 /** The parent of the current node. */ 060 private final AbstractNode parent; 061 062 /** The ast node. */ 063 private final DetailAST detailAst; 064 065 /** Depth of the node. */ 066 private final int depth; 067 068 /** Represents index among siblings. */ 069 private final int indexAmongSiblings; 070 071 /** The text attribute node. */ 072 private AttributeNode attributeNode = ATTRIBUTE_NODE_UNINITIALIZED; 073 074 /** 075 * Creates a new {@code ElementNode} instance. 076 * 077 * @param root {@code Node} root of the tree 078 * @param parent {@code Node} parent of the current node 079 * @param detailAst reference to {@code DetailAST} 080 * @param depth the current node depth in the hierarchy 081 * @param indexAmongSiblings the current node index among the parent children nodes 082 */ 083 public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst, 084 int depth, int indexAmongSiblings) { 085 super(root.getTreeInfo()); 086 this.parent = parent; 087 this.root = root; 088 this.detailAst = detailAst; 089 this.depth = depth; 090 this.indexAmongSiblings = indexAmongSiblings; 091 } 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 = ((ElementNode) child1).indexAmongSiblings; 129 final int index2 = ((ElementNode) 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 * Iterates children of the current node and 145 * recursively creates new Xpath-nodes. 146 * 147 * @return children list 148 */ 149 @Override 150 protected List<AbstractNode> createChildren() { 151 return XpathUtil.createChildren(root, this, detailAst.getFirstChild()); 152 } 153 154 /** 155 * Determine whether the node has any children. 156 * 157 * @return {@code true} is the node has any children. 158 */ 159 @Override 160 public boolean hasChildNodes() { 161 return detailAst.hasChildren(); 162 } 163 164 /** 165 * Returns attribute value. Throws {@code UnsupportedOperationException} in case, 166 * when name of the attribute is not equal to 'text'. 167 * 168 * @param namespace namespace 169 * @param localPart actual name of the attribute 170 * @return attribute value 171 */ 172 @Override 173 public String getAttributeValue(String namespace, String localPart) { 174 final String result; 175 if (TEXT_ATTRIBUTE_NAME.equals(localPart)) { 176 result = Optional.ofNullable(getAttributeNode()) 177 .map(AttributeNode::getStringValue) 178 .orElse(null); 179 } 180 else { 181 result = null; 182 } 183 return result; 184 } 185 186 /** 187 * Returns local part. 188 * 189 * @return local part 190 */ 191 @Override 192 public String getLocalPart() { 193 return TokenUtil.getTokenName(detailAst.getType()); 194 } 195 196 /** 197 * Returns type of the node. 198 * 199 * @return node kind 200 */ 201 @Override 202 public int getNodeKind() { 203 return Type.ELEMENT; 204 } 205 206 /** 207 * Returns parent. 208 * 209 * @return parent 210 */ 211 @Override 212 public NodeInfo getParent() { 213 return parent; 214 } 215 216 /** 217 * Returns root. 218 * 219 * @return root 220 */ 221 @Override 222 public NodeInfo getRoot() { 223 return root; 224 } 225 226 /** 227 * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case, 228 * when there is no axis iterator for given axisNumber. 229 * 230 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 231 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 232 * but none of the subclasses of the {@link AxisIterator} 233 * class has non-empty {@code close()} method. 234 * 235 * @param axisNumber element from {@code AxisInfo} 236 * @return {@code AxisIterator} object 237 */ 238 @Override 239 public AxisIterator iterateAxis(int axisNumber) { 240 final AxisIterator result; 241 switch (axisNumber) { 242 case AxisInfo.ANCESTOR: 243 result = new Navigator.AncestorEnumeration(this, false); 244 break; 245 case AxisInfo.ANCESTOR_OR_SELF: 246 result = new Navigator.AncestorEnumeration(this, true); 247 break; 248 case AxisInfo.ATTRIBUTE: 249 result = SingleNodeIterator.makeIterator(getAttributeNode()); 250 break; 251 case AxisInfo.CHILD: 252 if (hasChildNodes()) { 253 result = new ArrayIterator.OfNodes<>( 254 getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 255 } 256 else { 257 result = EmptyIterator.ofNodes(); 258 } 259 break; 260 case AxisInfo.DESCENDANT: 261 if (hasChildNodes()) { 262 result = new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN); 263 } 264 else { 265 result = EmptyIterator.ofNodes(); 266 } 267 break; 268 case AxisInfo.DESCENDANT_OR_SELF: 269 result = new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE); 270 break; 271 case AxisInfo.PARENT: 272 result = SingleNodeIterator.makeIterator(parent); 273 break; 274 case AxisInfo.SELF: 275 result = SingleNodeIterator.makeIterator(this); 276 break; 277 case AxisInfo.FOLLOWING_SIBLING: 278 result = getFollowingSiblingsIterator(); 279 break; 280 case AxisInfo.PRECEDING_SIBLING: 281 result = getPrecedingSiblingsIterator(); 282 break; 283 case AxisInfo.FOLLOWING: 284 result = new FollowingIterator(this); 285 break; 286 case AxisInfo.PRECEDING: 287 result = new PrecedingIterator(this); 288 break; 289 default: 290 throw throwUnsupportedOperationException(); 291 } 292 293 return result; 294 } 295 296 /** 297 * Returns line number. 298 * 299 * @return line number 300 */ 301 @Override 302 public int getLineNumber() { 303 return detailAst.getLineNo(); 304 } 305 306 /** 307 * Returns column number. 308 * 309 * @return column number 310 */ 311 @Override 312 public int getColumnNumber() { 313 return detailAst.getColumnNo(); 314 } 315 316 /** 317 * Getter method for token type. 318 * 319 * @return token type 320 */ 321 @Override 322 public int getTokenType() { 323 return detailAst.getType(); 324 } 325 326 /** 327 * Returns underlying node. 328 * 329 * @return underlying node 330 */ 331 @Override 332 public DetailAST getUnderlyingNode() { 333 return detailAst; 334 } 335 336 /** 337 * Returns preceding sibling axis iterator. 338 * 339 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 340 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 341 * but none of the subclasses of the {@link AxisIterator} 342 * class has non-empty {@code close()} method. 343 * 344 * @return iterator 345 */ 346 private AxisIterator getPrecedingSiblingsIterator() { 347 final AxisIterator result; 348 if (indexAmongSiblings == 0) { 349 result = EmptyIterator.ofNodes(); 350 } 351 else { 352 result = new ReverseListIterator(getPrecedingSiblings()); 353 } 354 return result; 355 } 356 357 /** 358 * Returns following sibling axis iterator. 359 * 360 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 361 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 362 * but none of the subclasses of the {@link AxisIterator} 363 * class has non-empty {@code close()} method. 364 * 365 * @return iterator 366 */ 367 private AxisIterator getFollowingSiblingsIterator() { 368 final AxisIterator result; 369 if (indexAmongSiblings == parent.getChildren().size() - 1) { 370 result = EmptyIterator.ofNodes(); 371 } 372 else { 373 result = new ArrayIterator.OfNodes<>( 374 getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 375 } 376 return result; 377 } 378 379 /** 380 * Returns following siblings of the current node. 381 * 382 * @return siblings 383 */ 384 private List<AbstractNode> getFollowingSiblings() { 385 final List<AbstractNode> siblings = parent.getChildren(); 386 return siblings.subList(indexAmongSiblings + 1, siblings.size()); 387 } 388 389 /** 390 * Returns preceding siblings of the current node. 391 * 392 * @return siblings 393 */ 394 private List<AbstractNode> getPrecedingSiblings() { 395 final List<AbstractNode> siblings = parent.getChildren(); 396 return siblings.subList(0, indexAmongSiblings); 397 } 398 399 /** 400 * Checks if token type supports {@code @text} attribute, 401 * extracts its value, creates {@code AttributeNode} object and returns it. 402 * Value can be accessed using {@code @text} attribute. 403 * 404 * @return attribute node if possible, otherwise the {@code null} value 405 */ 406 private AttributeNode getAttributeNode() { 407 if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) { 408 if (XpathUtil.supportsTextAttribute(detailAst)) { 409 attributeNode = new AttributeNode(TEXT_ATTRIBUTE_NAME, 410 XpathUtil.getTextAttributeValue(detailAst)); 411 } 412 else { 413 attributeNode = null; 414 } 415 } 416 return attributeNode; 417 } 418 419 /** 420 * Returns UnsupportedOperationException exception. 421 * 422 * @return UnsupportedOperationException exception 423 */ 424 private static UnsupportedOperationException throwUnsupportedOperationException() { 425 return new UnsupportedOperationException("Operation is not supported"); 426 } 427}