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; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 026import com.puppycrawl.tools.checkstyle.utils.XpathUtil; 027import net.sf.saxon.om.AxisInfo; 028import net.sf.saxon.om.NodeInfo; 029import net.sf.saxon.tree.iter.ArrayIterator; 030import net.sf.saxon.tree.iter.AxisIterator; 031import net.sf.saxon.tree.iter.EmptyIterator; 032import net.sf.saxon.tree.iter.SingleNodeIterator; 033import net.sf.saxon.tree.util.Navigator; 034import net.sf.saxon.type.Type; 035 036/** 037 * Represents element node of Xpath-tree. 038 * 039 */ 040public class ElementNode extends AbstractNode { 041 042 /** String literal for text attribute. */ 043 private static final String TEXT_ATTRIBUTE_NAME = "text"; 044 045 /** Constant for optimization. */ 046 private static final AbstractNode[] EMPTY_ABSTRACT_NODE_ARRAY = new AbstractNode[0]; 047 048 /** The root node. */ 049 private final AbstractNode root; 050 051 /** The parent of the current node. */ 052 private final AbstractNode parent; 053 054 /** The ast node. */ 055 private final DetailAST detailAst; 056 057 /** Represents text of the DetailAST. */ 058 private final String text; 059 060 /** Represents index among siblings. */ 061 private final int indexAmongSiblings; 062 063 /** The text attribute node. */ 064 private AttributeNode attributeNode; 065 066 /** 067 * Creates a new {@code ElementNode} instance. 068 * 069 * @param root {@code Node} root of the tree 070 * @param parent {@code Node} parent of the current node 071 * @param detailAst reference to {@code DetailAST} 072 */ 073 public ElementNode(AbstractNode root, AbstractNode parent, DetailAST detailAst) { 074 super(root.getTreeInfo()); 075 this.parent = parent; 076 this.root = root; 077 this.detailAst = detailAst; 078 text = TokenUtil.getTokenName(detailAst.getType()); 079 indexAmongSiblings = parent.getChildren().size(); 080 createTextAttribute(); 081 createChildren(); 082 } 083 084 /** 085 * Iterates children of the current node and 086 * recursively creates new Xpath-nodes. 087 */ 088 private void createChildren() { 089 DetailAST currentChild = detailAst.getFirstChild(); 090 while (currentChild != null) { 091 final AbstractNode child = new ElementNode(root, this, currentChild); 092 addChild(child); 093 currentChild = currentChild.getNextSibling(); 094 } 095 } 096 097 /** 098 * Returns attribute value. Throws {@code UnsupportedOperationException} in case, 099 * when name of the attribute is not equal to 'text'. 100 * @param namespace namespace 101 * @param localPart actual name of the attribute 102 * @return attribute value 103 */ 104 @Override 105 public String getAttributeValue(String namespace, String localPart) { 106 final String result; 107 if (TEXT_ATTRIBUTE_NAME.equals(localPart)) { 108 if (attributeNode == null) { 109 result = null; 110 } 111 else { 112 result = attributeNode.getStringValue(); 113 } 114 } 115 else { 116 result = null; 117 } 118 return result; 119 } 120 121 /** 122 * Returns local part. 123 * @return local part 124 */ 125 @Override 126 public String getLocalPart() { 127 return text; 128 } 129 130 /** 131 * Returns type of the node. 132 * @return node kind 133 */ 134 @Override 135 public int getNodeKind() { 136 return Type.ELEMENT; 137 } 138 139 /** 140 * Returns parent. 141 * @return parent 142 */ 143 @Override 144 public NodeInfo getParent() { 145 return parent; 146 } 147 148 /** 149 * Returns root. 150 * @return root 151 */ 152 @Override 153 public NodeInfo getRoot() { 154 return root; 155 } 156 157 /** 158 * Returns string value. 159 * @return string value 160 */ 161 @Override 162 public String getStringValue() { 163 return text; 164 } 165 166 /** 167 * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case, 168 * when there is no axis iterator for given axisNumber. 169 * 170 * @param axisNumber element from {@code AxisInfo} 171 * @return {@code AxisIterator} object 172 */ 173 @Override 174 public AxisIterator iterateAxis(byte axisNumber) { 175 final AxisIterator result; 176 switch (axisNumber) { 177 case AxisInfo.ANCESTOR: 178 try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, false)) { 179 result = iterator; 180 } 181 break; 182 case AxisInfo.ANCESTOR_OR_SELF: 183 try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, true)) { 184 result = iterator; 185 } 186 break; 187 case AxisInfo.ATTRIBUTE: 188 try (AxisIterator iterator = SingleNodeIterator.makeIterator(attributeNode)) { 189 result = iterator; 190 } 191 break; 192 case AxisInfo.CHILD: 193 if (hasChildNodes()) { 194 try (AxisIterator iterator = new ArrayIterator.OfNodes( 195 getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 196 result = iterator; 197 } 198 } 199 else { 200 result = EmptyIterator.OfNodes.THE_INSTANCE; 201 } 202 break; 203 case AxisInfo.DESCENDANT: 204 if (hasChildNodes()) { 205 try (AxisIterator iterator = 206 new Navigator.DescendantEnumeration(this, false, true)) { 207 result = iterator; 208 } 209 } 210 else { 211 result = EmptyIterator.OfNodes.THE_INSTANCE; 212 } 213 break; 214 case AxisInfo.DESCENDANT_OR_SELF: 215 try (AxisIterator iterator = 216 new Navigator.DescendantEnumeration(this, true, true)) { 217 result = iterator; 218 } 219 break; 220 case AxisInfo.PARENT: 221 try (AxisIterator iterator = SingleNodeIterator.makeIterator(parent)) { 222 result = iterator; 223 } 224 break; 225 case AxisInfo.SELF: 226 try (AxisIterator iterator = SingleNodeIterator.makeIterator(this)) { 227 result = iterator; 228 } 229 break; 230 case AxisInfo.FOLLOWING_SIBLING: 231 result = getFollowingSiblingsIterator(); 232 break; 233 case AxisInfo.PRECEDING_SIBLING: 234 result = getPrecedingSiblingsIterator(); 235 break; 236 case AxisInfo.FOLLOWING: 237 try (AxisIterator iterator = new FollowingEnumeration(this)) { 238 result = iterator; 239 } 240 break; 241 case AxisInfo.PRECEDING: 242 try (AxisIterator iterator = new Navigator.PrecedingEnumeration(this, true)) { 243 result = iterator; 244 } 245 break; 246 default: 247 throw throwUnsupportedOperationException(); 248 } 249 return result; 250 } 251 252 /** 253 * Returns line number. 254 * @return line number 255 */ 256 @Override 257 public int getLineNumber() { 258 return detailAst.getLineNo(); 259 } 260 261 /** 262 * Returns column number. 263 * @return column number 264 */ 265 @Override 266 public int getColumnNumber() { 267 return detailAst.getColumnNo(); 268 } 269 270 /** 271 * Getter method for token type. 272 * @return token type 273 */ 274 @Override 275 public int getTokenType() { 276 return detailAst.getType(); 277 } 278 279 /** 280 * Returns underlying node. 281 * @return underlying node 282 */ 283 @Override 284 public DetailAST getUnderlyingNode() { 285 return detailAst; 286 } 287 288 /** 289 * Returns preceding sibling axis iterator. 290 * @return iterator 291 */ 292 private AxisIterator getPrecedingSiblingsIterator() { 293 final AxisIterator result; 294 if (indexAmongSiblings == 0) { 295 result = EmptyIterator.OfNodes.THE_INSTANCE; 296 } 297 else { 298 try (AxisIterator iterator = new ArrayIterator.OfNodes( 299 getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 300 result = iterator; 301 } 302 } 303 return result; 304 } 305 306 /** 307 * Returns following sibling axis iterator. 308 * @return iterator 309 */ 310 private AxisIterator getFollowingSiblingsIterator() { 311 final AxisIterator result; 312 if (indexAmongSiblings == parent.getChildren().size() - 1) { 313 result = EmptyIterator.OfNodes.THE_INSTANCE; 314 } 315 else { 316 try (AxisIterator iterator = new ArrayIterator.OfNodes( 317 getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 318 result = iterator; 319 } 320 } 321 return result; 322 } 323 324 /** 325 * Returns following siblings of the current node. 326 * @return siblings 327 */ 328 private List<AbstractNode> getFollowingSiblings() { 329 final List<AbstractNode> siblings = parent.getChildren(); 330 return siblings.subList(indexAmongSiblings + 1, siblings.size()); 331 } 332 333 /** 334 * Returns preceding siblings of the current node. 335 * @return siblings 336 */ 337 private List<AbstractNode> getPrecedingSiblings() { 338 final List<AbstractNode> siblings = parent.getChildren(); 339 return siblings.subList(0, indexAmongSiblings); 340 } 341 342 /** 343 * Checks if token type supports {@code @text} attribute, 344 * extracts its value, creates {@code AttributeNode} object and returns it. 345 * Value can be accessed using {@code @text} attribute. 346 */ 347 private void createTextAttribute() { 348 AttributeNode attribute = null; 349 if (XpathUtil.supportsTextAttribute(detailAst)) { 350 attribute = new AttributeNode(TEXT_ATTRIBUTE_NAME, 351 XpathUtil.getTextAttributeValue(detailAst)); 352 } 353 attributeNode = attribute; 354 } 355 356 /** 357 * Returns UnsupportedOperationException exception. 358 * @return UnsupportedOperationException exception 359 */ 360 private static UnsupportedOperationException throwUnsupportedOperationException() { 361 return new UnsupportedOperationException("Operation is not supported"); 362 } 363 364 /** 365 * Implementation of the following axis, in terms of the child and following-sibling axes. 366 */ 367 private static final class FollowingEnumeration implements AxisIterator { 368 /** Following-sibling axis iterator. */ 369 private AxisIterator siblingEnum; 370 /** Child axis iterator. */ 371 private AxisIterator descendEnum; 372 373 /** 374 * Create an iterator over the "following" axis. 375 * @param start the initial context node. 376 */ 377 /* default */ FollowingEnumeration(NodeInfo start) { 378 siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING); 379 } 380 381 /** 382 * Get the next item in the sequence. 383 * @return the next Item. If there are no more nodes, return null. 384 */ 385 @Override 386 public NodeInfo next() { 387 NodeInfo result = null; 388 if (descendEnum != null) { 389 result = descendEnum.next(); 390 } 391 392 if (result == null) { 393 descendEnum = null; 394 result = siblingEnum.next(); 395 if (result == null) { 396 siblingEnum = null; 397 } 398 else { 399 descendEnum = new Navigator.DescendantEnumeration(result, true, false); 400 result = next(); 401 } 402 } 403 return result; 404 } 405 } 406 407}