001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 net.sf.saxon.om.AxisInfo; 029import net.sf.saxon.om.NodeInfo; 030import net.sf.saxon.tree.iter.ArrayIterator; 031import net.sf.saxon.tree.iter.AxisIterator; 032import net.sf.saxon.tree.iter.EmptyIterator; 033import net.sf.saxon.tree.iter.SingleNodeIterator; 034import net.sf.saxon.tree.util.Navigator; 035import net.sf.saxon.type.Type; 036 037/** 038 * Represents element node of Xpath-tree. 039 * 040 */ 041public class ElementNode extends AbstractNode { 042 043 /** String literal for text attribute. */ 044 private static final String TEXT_ATTRIBUTE_NAME = "text"; 045 046 /** Constant for optimization. */ 047 private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0]; 048 049 /** Holder value for lazy creation of attribute node. */ 050 private static final AttributeNode ATTRIBUTE_NODE_UNINITIALIZED = new AttributeNode(null, null); 051 052 /** The root node. */ 053 private final AbstractNode root; 054 055 /** The parent of the current node. */ 056 private final AbstractNode parent; 057 058 /** The ast node. */ 059 private final DetailAST detailAst; 060 061 /** Depth of the node. */ 062 private final int depth; 063 064 /** Represents index among siblings. */ 065 private final int indexAmongSiblings; 066 067 /** The text attribute node. */ 068 private AttributeNode attributeNode = ATTRIBUTE_NODE_UNINITIALIZED; 069 070 /** 071 * Creates a new {@code ElementNode} instance. 072 * 073 * @param root {@code Node} root of the tree 074 * @param parent {@code Node} parent of the current node 075 * @param detailAst reference to {@code DetailAST} 076 * @param depth the current node depth in the hierarchy 077 * @param indexAmongSiblings the current node index among the parent children nodes 078 */ 079 public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst, 080 int depth, int indexAmongSiblings) { 081 super(root.getTreeInfo()); 082 this.parent = parent; 083 this.root = root; 084 this.detailAst = detailAst; 085 this.depth = depth; 086 this.indexAmongSiblings = indexAmongSiblings; 087 } 088 089 /** 090 * Compares current object with specified for order. 091 * 092 * @param other another {@code NodeInfo} object 093 * @return number representing order of current object to specified one 094 */ 095 @Override 096 public int compareOrder(NodeInfo other) { 097 int result = 0; 098 if (other instanceof AbstractNode) { 099 result = Integer.compare(depth, ((AbstractNode) other).getDepth()); 100 if (result == 0) { 101 result = compareCommonAncestorChildrenOrder(this, other); 102 } 103 } 104 return result; 105 } 106 107 /** 108 * Walks up the hierarchy until a common ancestor is found. 109 * Then compares topmost sibling nodes. 110 * 111 * @param first {@code NodeInfo} to compare 112 * @param second {@code NodeInfo} to compare 113 * @return the value {@code 0} if {@code first == second}; 114 * a value less than {@code 0} if {@code first} should be first; 115 * a value greater than {@code 0} if {@code second} should be first. 116 */ 117 private static int compareCommonAncestorChildrenOrder(NodeInfo first, NodeInfo second) { 118 NodeInfo child1 = first; 119 NodeInfo child2 = second; 120 while (!child1.getParent().equals(child2.getParent())) { 121 child1 = child1.getParent(); 122 child2 = child2.getParent(); 123 } 124 final int index1 = ((ElementNode) child1).indexAmongSiblings; 125 final int index2 = ((ElementNode) child2).indexAmongSiblings; 126 return Integer.compare(index1, index2); 127 } 128 129 /** 130 * Getter method for node depth. 131 * 132 * @return depth 133 */ 134 @Override 135 public int getDepth() { 136 return depth; 137 } 138 139 /** 140 * Iterates children of the current node and 141 * recursively creates new Xpath-nodes. 142 * 143 * @return children list 144 */ 145 @Override 146 protected List<AbstractNode> createChildren() { 147 return XpathUtil.createChildren(root, this, detailAst.getFirstChild()); 148 } 149 150 /** 151 * Determine whether the node has any children. 152 * 153 * @return {@code true} is the node has any children. 154 */ 155 @Override 156 public boolean hasChildNodes() { 157 return detailAst.hasChildren(); 158 } 159 160 /** 161 * Returns attribute value. Throws {@code UnsupportedOperationException} in case, 162 * when name of the attribute is not equal to 'text'. 163 * 164 * @param namespace namespace 165 * @param localPart actual name of the attribute 166 * @return attribute value 167 */ 168 @Override 169 public String getAttributeValue(String namespace, String localPart) { 170 final String result; 171 if (TEXT_ATTRIBUTE_NAME.equals(localPart)) { 172 result = Optional.ofNullable(getAttributeNode()) 173 .map(AttributeNode::getStringValue) 174 .orElse(null); 175 } 176 else { 177 result = null; 178 } 179 return result; 180 } 181 182 /** 183 * Returns local part. 184 * 185 * @return local part 186 */ 187 @Override 188 public String getLocalPart() { 189 return TokenUtil.getTokenName(detailAst.getType()); 190 } 191 192 /** 193 * Returns type of the node. 194 * 195 * @return node kind 196 */ 197 @Override 198 public int getNodeKind() { 199 return Type.ELEMENT; 200 } 201 202 /** 203 * Returns parent. 204 * 205 * @return parent 206 */ 207 @Override 208 public NodeInfo getParent() { 209 return parent; 210 } 211 212 /** 213 * Returns root. 214 * 215 * @return root 216 */ 217 @Override 218 public NodeInfo getRoot() { 219 return root; 220 } 221 222 /** 223 * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case, 224 * when there is no axis iterator for given axisNumber. 225 * 226 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 227 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 228 * but none of the subclasses of the {@link AxisIterator} 229 * class has non-empty {@code close()} method. 230 * 231 * @param axisNumber element from {@code AxisInfo} 232 * @return {@code AxisIterator} object 233 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 234 */ 235 @Override 236 public AxisIterator iterateAxis(int axisNumber) { 237 final AxisIterator result; 238 switch (axisNumber) { 239 case AxisInfo.ANCESTOR: 240 result = new Navigator.AncestorEnumeration(this, false); 241 break; 242 case AxisInfo.ANCESTOR_OR_SELF: 243 result = new Navigator.AncestorEnumeration(this, true); 244 break; 245 case AxisInfo.ATTRIBUTE: 246 result = SingleNodeIterator.makeIterator(getAttributeNode()); 247 break; 248 case AxisInfo.CHILD: 249 if (hasChildNodes()) { 250 result = new ArrayIterator.OfNodes( 251 getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 252 } 253 else { 254 result = EmptyIterator.ofNodes(); 255 } 256 break; 257 case AxisInfo.DESCENDANT: 258 if (hasChildNodes()) { 259 result = new Navigator.DescendantEnumeration(this, false, true); 260 } 261 else { 262 result = EmptyIterator.ofNodes(); 263 } 264 break; 265 case AxisInfo.DESCENDANT_OR_SELF: 266 result = new Navigator.DescendantEnumeration(this, true, true); 267 break; 268 case AxisInfo.PARENT: 269 result = SingleNodeIterator.makeIterator(parent); 270 break; 271 case AxisInfo.SELF: 272 result = SingleNodeIterator.makeIterator(this); 273 break; 274 case AxisInfo.FOLLOWING_SIBLING: 275 result = getFollowingSiblingsIterator(); 276 break; 277 case AxisInfo.PRECEDING_SIBLING: 278 result = getPrecedingSiblingsIterator(); 279 break; 280 case AxisInfo.FOLLOWING: 281 result = new FollowingEnumeration(this); 282 break; 283 case AxisInfo.PRECEDING: 284 result = new Navigator.PrecedingEnumeration(this, true); 285 break; 286 default: 287 throw throwUnsupportedOperationException(); 288 } 289 290 return result; 291 } 292 293 /** 294 * Returns line number. 295 * 296 * @return line number 297 */ 298 @Override 299 public int getLineNumber() { 300 return detailAst.getLineNo(); 301 } 302 303 /** 304 * Returns column number. 305 * 306 * @return column number 307 */ 308 @Override 309 public int getColumnNumber() { 310 return detailAst.getColumnNo(); 311 } 312 313 /** 314 * Getter method for token type. 315 * 316 * @return token type 317 */ 318 @Override 319 public int getTokenType() { 320 return detailAst.getType(); 321 } 322 323 /** 324 * Returns underlying node. 325 * 326 * @return underlying node 327 */ 328 @Override 329 public DetailAST getUnderlyingNode() { 330 return detailAst; 331 } 332 333 /** 334 * Returns preceding sibling axis iterator. 335 * 336 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 337 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 338 * but none of the subclasses of the {@link AxisIterator} 339 * class has non-empty {@code close()} method. 340 * 341 * @return iterator 342 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 343 */ 344 private AxisIterator getPrecedingSiblingsIterator() { 345 final AxisIterator result; 346 if (indexAmongSiblings == 0) { 347 result = EmptyIterator.ofNodes(); 348 } 349 else { 350 result = new ArrayIterator.OfNodes( 351 getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY)); 352 } 353 return result; 354 } 355 356 /** 357 * Returns following sibling axis iterator. 358 * 359 * <p>Reason of suppression for resource, IOResourceOpenedButNotSafelyClosed: 360 * {@link AxisIterator} implements {@link java.io.Closeable} interface, 361 * but none of the subclasses of the {@link AxisIterator} 362 * class has non-empty {@code close()} method. 363 * 364 * @return iterator 365 * @noinspection resource, IOResourceOpenedButNotSafelyClosed 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 428 /** 429 * Implementation of the following axis, in terms of the child and following-sibling axes. 430 */ 431 private static final class FollowingEnumeration implements AxisIterator { 432 /** Following-sibling axis iterator. */ 433 private AxisIterator siblingEnum; 434 /** Child axis iterator. */ 435 private AxisIterator descendEnum; 436 437 /** 438 * Create an iterator over the "following" axis. 439 * 440 * @param start the initial context node. 441 */ 442 /* package */ FollowingEnumeration(NodeInfo start) { 443 siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING); 444 } 445 446 /** 447 * Get the next item in the sequence. 448 * 449 * @return the next Item. If there are no more nodes, return null. 450 */ 451 @Override 452 public NodeInfo next() { 453 NodeInfo result = null; 454 if (descendEnum != null) { 455 result = descendEnum.next(); 456 } 457 458 if (result == null) { 459 descendEnum = null; 460 result = siblingEnum.next(); 461 if (result == null) { 462 siblingEnum = null; 463 } 464 else { 465 descendEnum = new Navigator.DescendantEnumeration(result, true, false); 466 result = next(); 467 } 468 } 469 return result; 470 } 471 } 472 473}