001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 // -@cs[SimpleAccessorNameNotation] Overrides method from the base class. 126 // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166 127 @Override 128 public String getLocalPart() { 129 return text; 130 } 131 132 /** 133 * Returns type of the node. 134 * @return node kind 135 */ 136 @Override 137 public int getNodeKind() { 138 return Type.ELEMENT; 139 } 140 141 /** 142 * Returns parent. 143 * @return parent 144 */ 145 @Override 146 public NodeInfo getParent() { 147 return parent; 148 } 149 150 /** 151 * Returns root. 152 * @return root 153 */ 154 @Override 155 public NodeInfo getRoot() { 156 return root; 157 } 158 159 /** 160 * Returns string value. 161 * @return string value 162 */ 163 // -@cs[SimpleAccessorNameNotation] Overrides method from the base class. 164 // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166 165 @Override 166 public String getStringValue() { 167 return text; 168 } 169 170 /** 171 * Determines axis iteration algorithm. Throws {@code UnsupportedOperationException} in case, 172 * when there is no axis iterator for given axisNumber. 173 * 174 * @param axisNumber element from {@code AxisInfo} 175 * @return {@code AxisIterator} object 176 */ 177 @Override 178 public AxisIterator iterateAxis(byte axisNumber) { 179 final AxisIterator result; 180 switch (axisNumber) { 181 case AxisInfo.ANCESTOR: 182 try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, false)) { 183 result = iterator; 184 } 185 break; 186 case AxisInfo.ANCESTOR_OR_SELF: 187 try (AxisIterator iterator = new Navigator.AncestorEnumeration(this, true)) { 188 result = iterator; 189 } 190 break; 191 case AxisInfo.ATTRIBUTE: 192 try (AxisIterator iterator = SingleNodeIterator.makeIterator(attributeNode)) { 193 result = iterator; 194 } 195 break; 196 case AxisInfo.CHILD: 197 if (hasChildNodes()) { 198 try (AxisIterator iterator = new ArrayIterator.OfNodes( 199 getChildren().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 200 result = iterator; 201 } 202 } 203 else { 204 result = EmptyIterator.OfNodes.THE_INSTANCE; 205 } 206 break; 207 case AxisInfo.DESCENDANT: 208 if (hasChildNodes()) { 209 try (AxisIterator iterator = 210 new Navigator.DescendantEnumeration(this, false, true)) { 211 result = iterator; 212 } 213 } 214 else { 215 result = EmptyIterator.OfNodes.THE_INSTANCE; 216 } 217 break; 218 case AxisInfo.DESCENDANT_OR_SELF: 219 try (AxisIterator iterator = 220 new Navigator.DescendantEnumeration(this, true, true)) { 221 result = iterator; 222 } 223 break; 224 case AxisInfo.PARENT: 225 try (AxisIterator iterator = SingleNodeIterator.makeIterator(parent)) { 226 result = iterator; 227 } 228 break; 229 case AxisInfo.SELF: 230 try (AxisIterator iterator = SingleNodeIterator.makeIterator(this)) { 231 result = iterator; 232 } 233 break; 234 case AxisInfo.FOLLOWING_SIBLING: 235 result = getFollowingSiblingsIterator(); 236 break; 237 case AxisInfo.PRECEDING_SIBLING: 238 result = getPrecedingSiblingsIterator(); 239 break; 240 case AxisInfo.FOLLOWING: 241 try (AxisIterator iterator = new FollowingEnumeration(this)) { 242 result = iterator; 243 } 244 break; 245 case AxisInfo.PRECEDING: 246 try (AxisIterator iterator = new Navigator.PrecedingEnumeration(this, true)) { 247 result = iterator; 248 } 249 break; 250 default: 251 throw throwUnsupportedOperationException(); 252 } 253 return result; 254 } 255 256 /** 257 * Returns line number. 258 * @return line number 259 */ 260 @Override 261 public int getLineNumber() { 262 return detailAst.getLineNo(); 263 } 264 265 /** 266 * Returns column number. 267 * @return column number 268 */ 269 @Override 270 public int getColumnNumber() { 271 return detailAst.getColumnNo(); 272 } 273 274 /** 275 * Getter method for token type. 276 * @return token type 277 */ 278 @Override 279 public int getTokenType() { 280 return detailAst.getType(); 281 } 282 283 /** 284 * Returns underlying node. 285 * @return underlying node 286 */ 287 // -@cs[SimpleAccessorNameNotation] Overrides method from the base class. 288 // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166 289 @Override 290 public DetailAST getUnderlyingNode() { 291 return detailAst; 292 } 293 294 /** 295 * Returns preceding sibling axis iterator. 296 * @return iterator 297 */ 298 private AxisIterator getPrecedingSiblingsIterator() { 299 final AxisIterator result; 300 if (indexAmongSiblings == 0) { 301 result = EmptyIterator.OfNodes.THE_INSTANCE; 302 } 303 else { 304 try (AxisIterator iterator = new ArrayIterator.OfNodes( 305 getPrecedingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 306 result = iterator; 307 } 308 } 309 return result; 310 } 311 312 /** 313 * Returns following sibling axis iterator. 314 * @return iterator 315 */ 316 private AxisIterator getFollowingSiblingsIterator() { 317 final AxisIterator result; 318 if (indexAmongSiblings == parent.getChildren().size() - 1) { 319 result = EmptyIterator.OfNodes.THE_INSTANCE; 320 } 321 else { 322 try (AxisIterator iterator = new ArrayIterator.OfNodes( 323 getFollowingSiblings().toArray(EMPTY_ABSTRACT_NODE_ARRAY))) { 324 result = iterator; 325 } 326 } 327 return result; 328 } 329 330 /** 331 * Returns following siblings of the current node. 332 * @return siblings 333 */ 334 private List<AbstractNode> getFollowingSiblings() { 335 final List<AbstractNode> siblings = parent.getChildren(); 336 return siblings.subList(indexAmongSiblings + 1, siblings.size()); 337 } 338 339 /** 340 * Returns preceding siblings of the current node. 341 * @return siblings 342 */ 343 private List<AbstractNode> getPrecedingSiblings() { 344 final List<AbstractNode> siblings = parent.getChildren(); 345 return siblings.subList(0, indexAmongSiblings); 346 } 347 348 /** 349 * Checks if token type supports {@code @text} attribute, 350 * extracts its value, creates {@code AttributeNode} object and returns it. 351 * Value can be accessed using {@code @text} attribute. 352 */ 353 private void createTextAttribute() { 354 AttributeNode attribute = null; 355 if (XpathUtil.supportsTextAttribute(detailAst)) { 356 attribute = new AttributeNode(TEXT_ATTRIBUTE_NAME, 357 XpathUtil.getTextAttributeValue(detailAst)); 358 } 359 attributeNode = attribute; 360 } 361 362 /** 363 * Returns UnsupportedOperationException exception. 364 * @return UnsupportedOperationException exception 365 */ 366 private static UnsupportedOperationException throwUnsupportedOperationException() { 367 return new UnsupportedOperationException("Operation is not supported"); 368 } 369 370 /** 371 * Implementation of the following axis, in terms of the child and following-sibling axes. 372 */ 373 private static final class FollowingEnumeration implements AxisIterator { 374 /** Following-sibling axis iterator. */ 375 private AxisIterator siblingEnum; 376 /** Child axis iterator. */ 377 private AxisIterator descendEnum; 378 379 /** 380 * Create an iterator over the "following" axis. 381 * @param start the initial context node. 382 */ 383 /* default */ FollowingEnumeration(NodeInfo start) { 384 siblingEnum = start.iterateAxis(AxisInfo.FOLLOWING_SIBLING); 385 } 386 387 /** 388 * Get the next item in the sequence. 389 * @return the next Item. If there are no more nodes, return null. 390 */ 391 @Override 392 public NodeInfo next() { 393 NodeInfo result = null; 394 if (descendEnum != null) { 395 result = descendEnum.next(); 396 } 397 398 if (result == null) { 399 descendEnum = null; 400 result = siblingEnum.next(); 401 if (result == null) { 402 siblingEnum = null; 403 } 404 else { 405 descendEnum = new Navigator.DescendantEnumeration(result, true, false); 406 result = next(); 407 } 408 } 409 return result; 410 } 411 } 412 413}