001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code 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 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 238 */ 239 @Override 240 public AxisIterator iterateAxis(int axisNumber) { 241 final AxisIterator result; 242 switch (axisNumber) { 243 case AxisInfo.ANCESTOR: 244 result = new Navigator.AncestorEnumeration(this, false); 245 break; 246 case AxisInfo.ANCESTOR_OR_SELF: 247 result = new Navigator.AncestorEnumeration(this, true); 248 break; 249 case AxisInfo.ATTRIBUTE: 250 result = SingleNodeIterator.makeIterator(getAttributeNode()); 251 break; 252 case AxisInfo.CHILD: 253 if (hasChildNodes()) { 254 result = new ArrayIterator.OfNodes( 255 getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 256 } 257 else { 258 result = EmptyIterator.ofNodes(); 259 } 260 break; 261 case AxisInfo.DESCENDANT: 262 if (hasChildNodes()) { 263 result = new DescendantIterator(this, DescendantIterator.StartWith.CHILDREN); 264 } 265 else { 266 result = EmptyIterator.ofNodes(); 267 } 268 break; 269 case AxisInfo.DESCENDANT_OR_SELF: 270 result = new DescendantIterator(this, DescendantIterator.StartWith.CURRENT_NODE); 271 break; 272 case AxisInfo.PARENT: 273 result = SingleNodeIterator.makeIterator(parent); 274 break; 275 case AxisInfo.SELF: 276 result = SingleNodeIterator.makeIterator(this); 277 break; 278 case AxisInfo.FOLLOWING_SIBLING: 279 result = getFollowingSiblingsIterator(); 280 break; 281 case AxisInfo.PRECEDING_SIBLING: 282 result = getPrecedingSiblingsIterator(); 283 break; 284 case AxisInfo.FOLLOWING: 285 result = new FollowingIterator(this); 286 break; 287 case AxisInfo.PRECEDING: 288 result = new PrecedingIterator(this); 289 break; 290 default: 291 throw throwUnsupportedOperationException(); 292 } 293 294 return result; 295 } 296 297 /** 298 * Returns line number. 299 * 300 * @return line number 301 */ 302 @Override 303 public int getLineNumber() { 304 return detailAst.getLineNo(); 305 } 306 307 /** 308 * Returns column number. 309 * 310 * @return column number 311 */ 312 @Override 313 public int getColumnNumber() { 314 return detailAst.getColumnNo(); 315 } 316 317 /** 318 * Getter method for token type. 319 * 320 * @return token type 321 */ 322 @Override 323 public int getTokenType() { 324 return detailAst.getType(); 325 } 326 327 /** 328 * Returns underlying node. 329 * 330 * @return underlying node 331 */ 332 @Override 333 public DetailAST getUnderlyingNode() { 334 return detailAst; 335 } 336 337 /** 338 * Returns preceding sibling axis iterator. 339 * 340 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 341 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 342 * but none of the subclasses of the {@link AxisIterator} 343 * class has non-empty {@code close()} method. 344 * 345 * @return iterator 346 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 347 */ 348 private AxisIterator getPrecedingSiblingsIterator() { 349 final AxisIterator result; 350 if (indexAmongSiblings == 0) { 351 result = EmptyIterator.ofNodes(); 352 } 353 else { 354 result = new ReverseListIterator(getPrecedingSiblings()); 355 } 356 return result; 357 } 358 359 /** 360 * Returns following sibling axis iterator. 361 * 362 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 363 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 364 * but none of the subclasses of the {@link AxisIterator} 365 * class has non-empty {@code close()} method. 366 * 367 * @return iterator 368 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 369 */ 370 private AxisIterator getFollowingSiblingsIterator() { 371 final AxisIterator result; 372 if (indexAmongSiblings == parent.getChildren().size() - 1) { 373 result = EmptyIterator.ofNodes(); 374 } 375 else { 376 result = new ArrayIterator.OfNodes( 377 getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 378 } 379 return result; 380 } 381 382 /** 383 * Returns following siblings of the current node. 384 * 385 * @return siblings 386 */ 387 private List<AbstractNode> getFollowingSiblings() { 388 final List<AbstractNode> siblings = parent.getChildren(); 389 return siblings.subList(indexAmongSiblings + 1, siblings.size()); 390 } 391 392 /** 393 * Returns preceding siblings of the current node. 394 * 395 * @return siblings 396 */ 397 private List<AbstractNode> getPrecedingSiblings() { 398 final List<AbstractNode> siblings = parent.getChildren(); 399 return siblings.subList(0, indexAmongSiblings); 400 } 401 402 /** 403 * Checks if token type supports {@code @text} attribute, 404 * extracts its value, creates {@code AttributeNode} object and returns it. 405 * Value can be accessed using {@code @text} attribute. 406 * 407 * @return attribute node if possible, otherwise the {@code null} value 408 */ 409 private AttributeNode getAttributeNode() { 410 if (attributeNode == ATTRIBUTE_NODE_UNINITIALIZED) { 411 if (XpathUtil.supportsTextAttribute(detailAst)) { 412 attributeNode = new AttributeNode(TEXT_ATTRIBUTE_NAME, 413 XpathUtil.getTextAttributeValue(detailAst)); 414 } 415 else { 416 attributeNode = null; 417 } 418 } 419 return attributeNode; 420 } 421 422 /** 423 * Returns UnsupportedOperationException exception. 424 * 425 * @return UnsupportedOperationException exception 426 */ 427 private static UnsupportedOperationException throwUnsupportedOperationException() { 428 return new UnsupportedOperationException("Operation is not supported"); 429 } 430}