1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.xpath;
21  
22  import static com.puppycrawl.tools.checkstyle.internal.utils.XpathUtil.getXpathItems;
23  import static org.junit.Assert.assertArrayEquals;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertFalse;
26  import static org.junit.Assert.assertNull;
27  import static org.junit.Assert.assertTrue;
28  import static org.junit.Assert.fail;
29  
30  import java.io.File;
31  import java.util.List;
32  
33  import org.junit.Test;
34  
35  import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
36  import com.puppycrawl.tools.checkstyle.JavaParser;
37  import com.puppycrawl.tools.checkstyle.api.DetailAST;
38  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39  import net.sf.saxon.om.AxisInfo;
40  import net.sf.saxon.om.NodeInfo;
41  import net.sf.saxon.tree.iter.EmptyIterator;
42  
43  public class XpathMapperTest extends AbstractPathTestSupport {
44  
45      @Override
46      protected String getPackageLocation() {
47          return "com/puppycrawl/tools/checkstyle/xpath/xpathmapper";
48      }
49  
50      @Test
51      public void testFullPath() throws Exception {
52          final String xpath = "/CLASS_DEF/OBJBLOCK/METHOD_DEF[1]/SLIST/VARIABLE_DEF[2]";
53          final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
54          final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
55          final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
56                  TokenTypes.CLASS_DEF)
57                  .findFirstToken(TokenTypes.OBJBLOCK)
58                  .findFirstToken(TokenTypes.METHOD_DEF)
59                  .findFirstToken(TokenTypes.SLIST)
60                  .findFirstToken(TokenTypes.VARIABLE_DEF)
61                  .getNextSibling()
62                  .getNextSibling();
63          final DetailAST[] expected = {expectedVariableDefNode};
64          assertArrayEquals("Result nodes differ from expected", expected, actual);
65      }
66  
67      @Test
68      public void testParent() throws Exception {
69          final String xpath = "(//VARIABLE_DEF)[1]/..";
70          final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
71          final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
72          final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
73                  TokenTypes.CLASS_DEF)
74                  .findFirstToken(TokenTypes.OBJBLOCK)
75                  .findFirstToken(TokenTypes.METHOD_DEF)
76                  .findFirstToken(TokenTypes.SLIST);
77          final DetailAST[] expected = {expectedVariableDefNode};
78          assertArrayEquals("Result nodes differ from expected", expected, actual);
79      }
80  
81      @Test
82      public void testCurlyBrackets() throws Exception {
83          final String xpath = "(//RCURLY)[2]";
84          final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
85          final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
86          final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
87                  TokenTypes.CLASS_DEF)
88                  .findFirstToken(TokenTypes.OBJBLOCK)
89                  .findFirstToken(TokenTypes.METHOD_DEF)
90                  .findFirstToken(TokenTypes.SLIST)
91                  .findFirstToken(TokenTypes.RCURLY);
92          final DetailAST[] expected = {expectedVariableDefNode};
93          assertArrayEquals("Result nodes differ from expected", expected, actual);
94      }
95  
96      @Test
97      public void testOr() throws Exception {
98          final String xpath = "//CLASS_DEF | //METHOD_DEF";
99          final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
100         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
101         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
102                 TokenTypes.CLASS_DEF);
103         final DetailAST expectedMethodDefNode1 = expectedClassDefNode
104                 .findFirstToken(TokenTypes.OBJBLOCK)
105                 .findFirstToken(TokenTypes.METHOD_DEF);
106         final DetailAST expectedMethodDefNode2 = expectedMethodDefNode1.getNextSibling();
107         final DetailAST[] expected = {expectedClassDefNode, expectedMethodDefNode1,
108             expectedMethodDefNode2};
109         assertArrayEquals("Result nodes differ from expected", expected, actual);
110     }
111 
112     @Test
113     public void testComplexQueryOne() throws Exception {
114         final String xpath = "/CLASS_DEF | /CLASS_DEF/OBJBLOCK";
115         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
116         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
117         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
118                 TokenTypes.CLASS_DEF);
119         final DetailAST expectedObjblockNode = expectedClassDefNode
120                 .findFirstToken(TokenTypes.OBJBLOCK);
121         final DetailAST[] expected = {expectedClassDefNode, expectedObjblockNode};
122         assertArrayEquals("Result nodes differ from expected", expected, actual);
123     }
124 
125     @Test
126     public void testComplexQueryTwo() throws Exception {
127         final String xpath = "/PACKAGE_DEF | /PACKAGE_DEF/ANNOTATIONS";
128         final RootNode rootNode = getRootNode("InputXpathMapperAnnotation.java");
129         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
130         final DetailAST expectedPackageDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
131                 TokenTypes.PACKAGE_DEF);
132         final DetailAST expectedAnnotationsNode = expectedPackageDefNode
133                 .findFirstToken(TokenTypes.ANNOTATIONS);
134         final DetailAST[] expected = {expectedAnnotationsNode, expectedPackageDefNode};
135         assertArrayEquals("Result nodes differ from expected", expected, actual);
136     }
137 
138     @Test
139     public void testComplexQueryThree() throws Exception {
140         final String xpath = "//CLASS_DEF | //CLASS_DEF//METHOD_DEF | /CLASS_DEF/OBJBLOCK";
141         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
142         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
143         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
144                 TokenTypes.CLASS_DEF);
145         final DetailAST expectedObjblockNode = expectedClassDefNode
146                 .findFirstToken(TokenTypes.OBJBLOCK);
147         final DetailAST expectedMethodDefNode = expectedObjblockNode
148                 .findFirstToken(TokenTypes.METHOD_DEF);
149         final DetailAST expectedMethodDefNode2 = expectedObjblockNode
150                 .findFirstToken(TokenTypes.METHOD_DEF)
151                 .getNextSibling();
152         final DetailAST[] expected = {expectedClassDefNode, expectedMethodDefNode,
153             expectedMethodDefNode2, expectedObjblockNode};
154         assertArrayEquals("Result nodes differ from expected", expected, actual);
155     }
156 
157     @Test
158     public void testAttributeOr() throws Exception {
159         final String xpath = "//METHOD_DEF[./IDENT[@text='getSomeMethod'] "
160                 + "or ./IDENT[@text='nonExistentMethod']]";
161         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
162         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
163         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
164                 TokenTypes.CLASS_DEF);
165         final DetailAST expectedMethodDefNode = expectedClassDefNode
166                 .findFirstToken(TokenTypes.OBJBLOCK)
167                 .findFirstToken(TokenTypes.METHOD_DEF)
168                 .getNextSibling();
169         final DetailAST[] expected = {expectedMethodDefNode};
170         assertArrayEquals("Result nodes differ from expected", expected, actual);
171     }
172 
173     @Test
174     public void testAttributeAnd() throws Exception {
175         final String xpath = "//METHOD_DEF[./IDENT[@text='callSomeMethod'] and "
176                 + "../..[./IDENT[@text='InputXpathMapperAst']]]";
177         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
178         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
179         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
180                 TokenTypes.CLASS_DEF);
181         final DetailAST expectedMethodDefNode = expectedClassDefNode
182                 .findFirstToken(TokenTypes.OBJBLOCK)
183                 .findFirstToken(TokenTypes.METHOD_DEF);
184         final DetailAST[] expected = {expectedMethodDefNode};
185         assertArrayEquals("Result nodes differ from expected", expected, actual);
186     }
187 
188     @Test
189     public void testQueryAllElementsWithAttribute() throws Exception {
190         final String xpath = "//*[./IDENT[@text]]";
191         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
192         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
193         assertEquals("Invalid number of nodes", 18, nodes.size());
194     }
195 
196     @Test
197     public void testQueryElementByIndex() throws Exception {
198         final String xpath = "(//VARIABLE_DEF)[1]";
199         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
200         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
201         assertEquals("Invalid number of nodes", 1, actual.length);
202         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
203                 TokenTypes.CLASS_DEF)
204                 .findFirstToken(TokenTypes.OBJBLOCK)
205                 .findFirstToken(TokenTypes.METHOD_DEF)
206                 .findFirstToken(TokenTypes.SLIST)
207                 .findFirstToken(TokenTypes.VARIABLE_DEF);
208         final DetailAST[] expected = {expectedVariableDefNode};
209         assertArrayEquals("Result nodes differ from expected", expected, actual);
210     }
211 
212     @Test
213     public void testQueryAllVariableDefinitionsWithAttribute() throws Exception {
214         final String xpath = "//VARIABLE_DEF[./IDENT[@*]]";
215         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
216         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
217         assertEquals("Invalid number of nodes", 4, nodes.size());
218     }
219 
220     @Test
221     public void testQueryAllVariableDefWrongAttribute() throws Exception {
222         final String xpath = "//VARIABLE_DEF[@qwe]";
223         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
224         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
225         assertEquals("Invalid number of nodes", 0, nodes.size());
226     }
227 
228     @Test
229     public void testQueryAllMethodDefinitionsInContext() throws Exception {
230         final String objectXpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]//OBJBLOCK";
231         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
232         final List<NodeInfo> objectNodes = getXpathItems(objectXpath, rootNode);
233         assertEquals("Invalid number of nodes", 1, objectNodes.size());
234         final AbstractNode objNode = (AbstractNode) objectNodes.get(0);
235         final String methodsXpath = "METHOD_DEF";
236         final List<NodeInfo> methodsNodes = getXpathItems(methodsXpath, objNode);
237         assertEquals("Invalid number of nodes", 2, methodsNodes.size());
238         final DetailAST[] actual = convertToArray(methodsNodes);
239         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
240                 TokenTypes.CLASS_DEF)
241                 .findFirstToken(TokenTypes.OBJBLOCK)
242                 .findFirstToken(TokenTypes.METHOD_DEF);
243         final DetailAST[] expected = {expectedMethodDefNode,
244             expectedMethodDefNode.getNextSibling()};
245         assertArrayEquals("Result nodes differ from expected", expected, actual);
246     }
247 
248     @Test
249     public void testQueryAllClassDefinitions() throws Exception {
250         final String xpath = "CLASS_DEF";
251         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
252         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
253         assertEquals("Invalid number of nodes", 1, nodes.size());
254         final AbstractNode classDefNode = (AbstractNode) nodes.get(0);
255         assertEquals("Invalid number of nodes", 3, classDefNode.getLineNumber());
256         assertEquals("Invalid number of nodes", 0, classDefNode.getColumnNumber());
257         final DetailAST[] actual = convertToArray(nodes);
258         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
259                 TokenTypes.CLASS_DEF);
260         final DetailAST[] expected = {expectedClassDefNode};
261         assertArrayEquals("Result nodes differ from expected", expected, actual);
262     }
263 
264     @Test
265     public void testQueryByMethodName() throws Exception {
266         final String xpath = "//METHOD_DEF[./IDENT[@text='getSomeMethod']]";
267         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
268         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
269         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
270                 TokenTypes.CLASS_DEF)
271                 .findFirstToken(TokenTypes.OBJBLOCK)
272                 .findFirstToken(TokenTypes.METHOD_DEF)
273                 .getNextSibling();
274         final DetailAST[] expected = {expectedMethodDefNode};
275         assertArrayEquals("Result nodes differ from expected", expected, actual);
276     }
277 
278     @Test
279     public void testQueryMethodDefinitionsByClassName() throws Exception {
280         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]"
281                 + "//OBJBLOCK//METHOD_DEF";
282         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
283         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
284         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
285                 TokenTypes.CLASS_DEF)
286                 .findFirstToken(TokenTypes.OBJBLOCK)
287                 .findFirstToken(TokenTypes.METHOD_DEF);
288         final DetailAST[] expected = {expectedMethodDefNode,
289             expectedMethodDefNode.getNextSibling()};
290         assertArrayEquals("Result nodes differ from expected", expected, actual);
291     }
292 
293     @Test
294     public void testQueryByClassNameAndMethodName() throws Exception {
295         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]//OBJBLOCK"
296                 + "//METHOD_DEF[./IDENT[@text='getSomeMethod']]";
297         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
298         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
299         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
300                 TokenTypes.CLASS_DEF)
301                 .findFirstToken(TokenTypes.OBJBLOCK)
302                 .findFirstToken(TokenTypes.METHOD_DEF)
303                 .getNextSibling();
304         final DetailAST[] expected = {expectedMethodDefNode};
305         assertArrayEquals("Result nodes differ from expected", expected, actual);
306     }
307 
308     @Test
309     public void testQueryClassDefinitionByClassName() throws Exception {
310         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]";
311         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
312         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
313         final DetailAST[] actual = convertToArray(nodes);
314         final DetailAST expectedClassDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
315                 TokenTypes.CLASS_DEF);
316         final DetailAST[] expected = {expectedClassDefNode};
317         final ElementNode classDefNode = (ElementNode) nodes.get(0);
318         assertEquals("Invalid number of nodes", "CLASS_DEF", classDefNode.getStringValue());
319         assertArrayEquals("Result nodes differ from expected", expected, actual);
320     }
321 
322     @Test
323     public void testQueryWrongClassName() throws Exception {
324         final String xpath = "/CLASS_DEF[@text='WrongName']";
325         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
326         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
327         assertTrue("Should return true, because no item matches xpath", nodes.isEmpty());
328     }
329 
330     @Test
331     public void testQueryWrongXpath() throws Exception {
332         final String xpath = "/WRONG_XPATH";
333         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
334         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
335         assertTrue("Should return true, because no item matches xpath", nodes.isEmpty());
336     }
337 
338     @Test
339     public void testQueryAncestor() throws Exception {
340         final String xpath = "//VARIABLE_DEF[./IDENT[@text='another']]/ancestor::METHOD_DEF";
341         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
342         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
343         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
344                 TokenTypes.CLASS_DEF)
345                 .findFirstToken(TokenTypes.OBJBLOCK)
346                 .findFirstToken(TokenTypes.METHOD_DEF);
347         final DetailAST[] expected = {expectedMethodDefNode};
348         assertArrayEquals("Result nodes differ from expected", expected, actual);
349     }
350 
351     @Test
352     public void testQueryAncestorOrSelf() throws Exception {
353         final String xpath = "//VARIABLE_DEF[./IDENT[@text='another']]"
354                 + "/ancestor-or-self::VARIABLE_DEF";
355         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
356         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
357         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
358                 TokenTypes.CLASS_DEF)
359                 .findFirstToken(TokenTypes.OBJBLOCK)
360                 .findFirstToken(TokenTypes.METHOD_DEF)
361                 .findFirstToken(TokenTypes.SLIST)
362                 .findFirstToken(TokenTypes.VARIABLE_DEF)
363                 .getNextSibling()
364                 .getNextSibling();
365         final DetailAST[] expected = {expectedVariableDefNode};
366         assertArrayEquals("Result nodes differ from expected", expected, actual);
367     }
368 
369     @Test
370     public void testQueryDescendant() throws Exception {
371         final String xpath = "//METHOD_DEF/descendant::EXPR";
372         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
373         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
374         assertEquals("Invalid number of nodes", 6, nodes.size());
375     }
376 
377     @Test
378     public void testQueryDescendantOrSelf() throws Exception {
379         final String xpath = "//METHOD_DEF/descendant-or-self::METHOD_DEF";
380         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
381         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
382         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
383                 TokenTypes.CLASS_DEF)
384                 .findFirstToken(TokenTypes.OBJBLOCK)
385                 .findFirstToken(TokenTypes.METHOD_DEF);
386         final DetailAST[] expected = {expectedMethodDefNode,
387             expectedMethodDefNode.getNextSibling()};
388         assertArrayEquals("Result nodes differ from expected", expected, actual);
389     }
390 
391     @Test
392     public void testQueryNoChild() throws Exception {
393         final String xpath = "//RCURLY/METHOD_DEF";
394         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
395         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
396         assertTrue("Should return true, because no item matches xpath", nodes.isEmpty());
397     }
398 
399     @Test
400     public void testQueryNoDescendant() throws Exception {
401         final String xpath = "//RCURLY/descendant::METHOD_DEF";
402         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
403         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
404         assertTrue("Should return true, because no item matches xpath", nodes.isEmpty());
405     }
406 
407     @Test
408     public void testQueryRootNotImplementedAxis() throws Exception {
409         final String xpath = "//following-sibling::METHOD_DEF";
410         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
411         try {
412             getXpathItems(xpath, rootNode);
413             fail("Exception is excepted");
414         }
415         catch (UnsupportedOperationException ex) {
416             assertEquals("Invalid number of nodes", "Operation is not supported", ex.getMessage());
417         }
418     }
419 
420     @Test
421     public void testQueryElementNotImplementedAxis() throws Exception {
422         final String xpath = "/CLASS_DEF//following-sibling::METHOD_DEF";
423         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
424         try {
425             getXpathItems(xpath, rootNode);
426             fail("Exception is excepted");
427         }
428         catch (UnsupportedOperationException ex) {
429             assertEquals("Invalid number of nodes", "Operation is not supported", ex.getMessage());
430         }
431     }
432 
433     @Test
434     public void testQuerySelf() throws Exception {
435         final String objectXpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]//OBJBLOCK";
436         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
437         final List<NodeInfo> objectNodes = getXpathItems(objectXpath, rootNode);
438         assertEquals("Invalid number of nodes", 1, objectNodes.size());
439         final AbstractNode objNode = (AbstractNode) objectNodes.get(0);
440         final String methodsXpath = "self::OBJBLOCK";
441         final DetailAST[] actual = convertToArray(getXpathItems(methodsXpath, objNode));
442         final DetailAST expectedObjBlockNode = getSiblingByType(rootNode.getUnderlyingNode(),
443                 TokenTypes.CLASS_DEF)
444                 .findFirstToken(TokenTypes.OBJBLOCK);
445         final DetailAST[] expected = {expectedObjBlockNode};
446         assertArrayEquals("Result nodes differ from expected", expected, actual);
447     }
448 
449     @Test
450     public void testRootWithNullDetailAst() {
451         final RootNode emptyRootNode = new RootNode(null);
452         assertFalse("Empty node should not have children", emptyRootNode.hasChildNodes());
453         assertEquals("Invalid number of nodes", EmptyIterator.OfNodes.THE_INSTANCE,
454                 emptyRootNode.iterateAxis(
455                 AxisInfo.DESCENDANT));
456         assertEquals("Invalid number of nodes", EmptyIterator.OfNodes.THE_INSTANCE,
457                 emptyRootNode.iterateAxis(AxisInfo.CHILD));
458     }
459 
460     @Test
461     public void testQueryNonExistentAttribute() throws Exception {
462         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperAst']]";
463         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
464         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
465         final ElementNode classDefNode = (ElementNode) nodes.get(0);
466         assertNull("Not existing attribute should have null value",
467                 classDefNode.getAttributeValue("", "noneExistingAttribute"));
468     }
469 
470     @Test
471     public void testQueryRootSelf() throws Exception {
472         final String xpath = "self::node()";
473         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
474         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
475         assertEquals("Invalid number of nodes", 1, nodes.size());
476     }
477 
478     @Test
479     public void testQueryAnnotation() throws Exception {
480         final String xpath = "//ANNOTATION[./IDENT[@text='Deprecated']]";
481         final RootNode rootNode = getRootNode("InputXpathMapperAnnotation.java");
482         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
483         final DetailAST expectedAnnotationNode = getSiblingByType(rootNode.getUnderlyingNode(),
484                 TokenTypes.CLASS_DEF)
485                 .findFirstToken(TokenTypes.MODIFIERS)
486                 .findFirstToken(TokenTypes.ANNOTATION);
487         final DetailAST[] expected = {expectedAnnotationNode};
488         assertArrayEquals("Result nodes differ from expected", expected, actual);
489     }
490 
491     @Test
492     public void testQueryNonExistentAnnotation() throws Exception {
493         final String xpath = "//ANNOTATION[@text='SpringBootApplication']";
494         final RootNode rootNode = getRootNode("InputXpathMapperAnnotation.java");
495         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
496         assertEquals("Invalid number of nodes", 0, nodes.size());
497     }
498 
499     @Test
500     public void testQueryEnumDef() throws Exception {
501         final String xpath = "/ENUM_DEF";
502         final RootNode enumRootNode = getRootNode("InputXpathMapperEnum.java");
503         final DetailAST[] actual = convertToArray(getXpathItems(xpath, enumRootNode));
504         final DetailAST expectedEnumDefNode = getSiblingByType(enumRootNode.getUnderlyingNode(),
505                 TokenTypes.ENUM_DEF);
506         final DetailAST[] expected = {expectedEnumDefNode};
507         assertArrayEquals("Result nodes differ from expected", expected, actual);
508     }
509 
510     @Test
511     public void testQueryEnumElementsNumber() throws Exception {
512         final String xpath = "/ENUM_DEF/OBJBLOCK/ENUM_CONSTANT_DEF";
513         final RootNode enumRootNode = getRootNode("InputXpathMapperEnum.java");
514         final List<NodeInfo> nodes = getXpathItems(xpath, enumRootNode);
515         assertEquals("Invalid number of nodes", 3, nodes.size());
516     }
517 
518     @Test
519     public void testQueryEnumElementByName() throws Exception {
520         final String xpath = "//*[./IDENT[@text='TWO']]";
521         final RootNode enumRootNode = getRootNode("InputXpathMapperEnum.java");
522         final DetailAST[] actual = convertToArray(getXpathItems(xpath, enumRootNode));
523         final DetailAST expectedEnumConstantDefNode = getSiblingByType(
524                 enumRootNode.getUnderlyingNode(),
525                 TokenTypes.ENUM_DEF)
526                 .findFirstToken(TokenTypes.OBJBLOCK)
527                 .findFirstToken(TokenTypes.ENUM_CONSTANT_DEF)
528                 .getNextSibling()
529                 .getNextSibling();
530         final DetailAST[] expected = {expectedEnumConstantDefNode};
531         assertArrayEquals("Result nodes differ from expected", expected, actual);
532     }
533 
534     @Test
535     public void testQueryInterfaceDef() throws Exception {
536         final String xpath = "/INTERFACE_DEF";
537         final RootNode interfaceRootNode = getRootNode("InputXpathMapperInterface.java");
538         final DetailAST[] actual = convertToArray(getXpathItems(xpath, interfaceRootNode));
539         final DetailAST expectedInterfaceDefNode = getSiblingByType(
540                 interfaceRootNode.getUnderlyingNode(),
541                 TokenTypes.INTERFACE_DEF);
542         final DetailAST[] expected = {expectedInterfaceDefNode};
543         assertArrayEquals("Result nodes differ from expected", expected, actual);
544     }
545 
546     @Test
547     public void testQueryInterfaceMethodDefNumber() throws Exception {
548         final String xpath = "/INTERFACE_DEF/OBJBLOCK/METHOD_DEF";
549         final RootNode interfaceRootNode = getRootNode("InputXpathMapperInterface.java");
550         final List<NodeInfo> nodes = getXpathItems(xpath, interfaceRootNode);
551         assertEquals("Invalid number of nodes", 4, nodes.size());
552     }
553 
554     @Test
555     public void testQueryInterfaceParameterDef() throws Exception {
556         final String xpath = "//PARAMETER_DEF[./IDENT[@text='someVariable']]/../..";
557         final RootNode interfaceRootNode = getRootNode("InputXpathMapperInterface.java");
558         final DetailAST[] actual = convertToArray(getXpathItems(xpath, interfaceRootNode));
559         final DetailAST expectedMethodDefNode = getSiblingByType(
560                 interfaceRootNode.getUnderlyingNode(),
561                 TokenTypes.INTERFACE_DEF)
562                 .findFirstToken(TokenTypes.OBJBLOCK)
563                 .findFirstToken(TokenTypes.METHOD_DEF)
564                 .getNextSibling();
565         final DetailAST[] expected = {expectedMethodDefNode};
566         assertArrayEquals("Result nodes differ from expected", expected, actual);
567     }
568 
569     @Test
570     public void testIdent() throws Exception {
571         final String xpath = "/CLASS_DEF/IDENT[@text='InputXpathMapperAst']";
572         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
573         final List<NodeInfo> nodes = getXpathItems(xpath, rootNode);
574         final DetailAST[] actual = convertToArray(nodes);
575         final DetailAST expectedIdentNode = getSiblingByType(rootNode.getUnderlyingNode(),
576                 TokenTypes.CLASS_DEF)
577                 .findFirstToken(TokenTypes.IDENT);
578 
579         final DetailAST[] expected = {expectedIdentNode};
580         assertArrayEquals("Result nodes differ from expected", expected, actual);
581     }
582 
583     @Test
584     public void testIdentByText() throws Exception {
585         final String xpath = "//IDENT[@text='puppycrawl']";
586         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
587         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
588         final DetailAST expectedMethodDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
589                 TokenTypes.PACKAGE_DEF)
590                 .findFirstToken(TokenTypes.DOT)
591                 .findFirstToken(TokenTypes.DOT)
592                 .findFirstToken(TokenTypes.DOT)
593                 .findFirstToken(TokenTypes.DOT)
594                 .findFirstToken(TokenTypes.DOT)
595                 .findFirstToken(TokenTypes.IDENT)
596                 .getNextSibling();
597         final DetailAST[] expected = {expectedMethodDefNode};
598         assertArrayEquals("Result nodes differ from expected", expected, actual);
599     }
600 
601     @Test
602     public void testNumVariableByItsValue() throws Exception {
603         final String xpath = "//VARIABLE_DEF[.//NUM_INT[@text=123]]";
604         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
605         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
606         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
607                 TokenTypes.CLASS_DEF)
608                 .findFirstToken(TokenTypes.OBJBLOCK)
609                 .findFirstToken(TokenTypes.METHOD_DEF)
610                 .findFirstToken(TokenTypes.SLIST)
611                 .findFirstToken(TokenTypes.VARIABLE_DEF);
612         final DetailAST[] expected = {expectedVariableDefNode};
613         assertArrayEquals("Result nodes differ from expected", expected, actual);
614     }
615 
616     @Test
617     public void testStringVariableByItsValue() throws Exception {
618         final String xpath = "//VARIABLE_DEF[./ASSIGN/EXPR"
619                 + "/STRING_LITERAL[@text='HelloWorld']]";
620         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
621         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
622         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
623                 TokenTypes.CLASS_DEF)
624                 .findFirstToken(TokenTypes.OBJBLOCK)
625                 .findFirstToken(TokenTypes.METHOD_DEF)
626                 .findFirstToken(TokenTypes.SLIST)
627                 .findFirstToken(TokenTypes.VARIABLE_DEF)
628                 .getNextSibling()
629                 .getNextSibling();
630         final DetailAST[] expected = {expectedVariableDefNode};
631         assertArrayEquals("Result nodes differ from expected", expected, actual);
632     }
633 
634     @Test
635     public void testSameNodesByNameAndByText() throws Exception {
636         final String xpath1 = "//VARIABLE_DEF[./IDENT[@text='another']]/ASSIGN/EXPR/STRING_LITERAL";
637         final String xpath2 = "//VARIABLE_DEF/ASSIGN/EXPR/STRING_LITERAL[@text='HelloWorld']";
638         final RootNode rootNode = getRootNode("InputXpathMapperAst.java");
639         final DetailAST[] actual1 = convertToArray(getXpathItems(xpath1, rootNode));
640         final DetailAST[] actual2 = convertToArray(getXpathItems(xpath2, rootNode));
641         assertArrayEquals("Result nodes differ from expected", actual1, actual2);
642     }
643 
644     @Test
645     public void testMethodDefByAnnotationValue() throws Exception {
646         final String xpath = "//METHOD_DEF[.//ANNOTATION[./IDENT[@text='SuppressWarnings']"
647                 + " and .//*[@text='good']]]";
648         final RootNode rootNode = getRootNode("InputXpathMapperAnnotation.java");
649         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
650         final DetailAST expectedAnnotationNode = getSiblingByType(rootNode.getUnderlyingNode(),
651                 TokenTypes.CLASS_DEF)
652                 .findFirstToken(TokenTypes.OBJBLOCK)
653                 .findFirstToken(TokenTypes.METHOD_DEF)
654                 .getNextSibling();
655         final DetailAST[] expected = {expectedAnnotationNode};
656         assertArrayEquals("Result nodes differ from expected", expected, actual);
657     }
658 
659     @Test
660     public void testFirstImport() throws Exception {
661         final String xpath = "/IMPORT[1]";
662         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
663         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
664         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
665                 TokenTypes.IMPORT);
666         final DetailAST[] expected = {expectedVariableDefNode};
667         assertArrayEquals("Result nodes differ from expected", expected, actual);
668     }
669 
670     @Test
671     public void testSecondImport() throws Exception {
672         final String xpath = "/IMPORT[2]";
673         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
674         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
675         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
676                 TokenTypes.IMPORT).getNextSibling();
677         final DetailAST[] expected = {expectedVariableDefNode};
678         assertArrayEquals("Result nodes differ from expected", expected, actual);
679     }
680 
681     @Test
682     public void testThirdImport() throws Exception {
683         final String xpath = "/IMPORT[3]";
684         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
685         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
686         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
687                 TokenTypes.IMPORT).getNextSibling().getNextSibling();
688         final DetailAST[] expected = {expectedVariableDefNode};
689         assertArrayEquals("Result nodes differ from expected", expected, actual);
690     }
691 
692     @Test
693     public void testLastImport() throws Exception {
694         final String xpath = "/IMPORT[9]";
695         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
696         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
697         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
698                 TokenTypes.IMPORT)
699                 .getNextSibling()
700                 .getNextSibling()
701                 .getNextSibling()
702                 .getNextSibling()
703                 .getNextSibling()
704                 .getNextSibling()
705                 .getNextSibling()
706                 .getNextSibling();
707         final DetailAST[] expected = {expectedVariableDefNode};
708         assertArrayEquals("Result nodes differ from expected", expected, actual);
709     }
710 
711     @Test
712     public void testFirstCaseGroup() throws Exception {
713         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperPositions']]"
714                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='switchMethod']]"
715                 + "/SLIST/LITERAL_SWITCH/CASE_GROUP[1]";
716         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
717         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
718         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
719                 TokenTypes.CLASS_DEF)
720                 .findFirstToken(TokenTypes.OBJBLOCK)
721                 .findFirstToken(TokenTypes.METHOD_DEF)
722                 .findFirstToken(TokenTypes.SLIST)
723                 .findFirstToken(TokenTypes.LITERAL_SWITCH)
724                 .findFirstToken(TokenTypes.CASE_GROUP);
725         final DetailAST[] expected = {expectedVariableDefNode};
726         assertArrayEquals("Result nodes differ from expected", expected, actual);
727     }
728 
729     @Test
730     public void testSecondCaseGroup() throws Exception {
731         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperPositions']]"
732                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='switchMethod']]"
733                 + "/SLIST/LITERAL_SWITCH/CASE_GROUP[2]";
734         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
735         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
736         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
737                 TokenTypes.CLASS_DEF)
738                 .findFirstToken(TokenTypes.OBJBLOCK)
739                 .findFirstToken(TokenTypes.METHOD_DEF)
740                 .findFirstToken(TokenTypes.SLIST)
741                 .findFirstToken(TokenTypes.LITERAL_SWITCH)
742                 .findFirstToken(TokenTypes.CASE_GROUP)
743                 .getNextSibling();
744         final DetailAST[] expected = {expectedVariableDefNode};
745         assertArrayEquals("Result nodes differ from expected", expected, actual);
746     }
747 
748     @Test
749     public void testThirdCaseGroup() throws Exception {
750         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperPositions']]"
751                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='switchMethod']]"
752                 + "/SLIST/LITERAL_SWITCH/CASE_GROUP[3]";
753         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
754         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
755         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
756                 TokenTypes.CLASS_DEF)
757                 .findFirstToken(TokenTypes.OBJBLOCK)
758                 .findFirstToken(TokenTypes.METHOD_DEF)
759                 .findFirstToken(TokenTypes.SLIST)
760                 .findFirstToken(TokenTypes.LITERAL_SWITCH)
761                 .findFirstToken(TokenTypes.CASE_GROUP)
762                 .getNextSibling()
763                 .getNextSibling();
764         final DetailAST[] expected = {expectedVariableDefNode};
765         assertArrayEquals("Result nodes differ from expected", expected, actual);
766     }
767 
768     @Test
769     public void testFourthCaseGroup() throws Exception {
770         final String xpath = "/CLASS_DEF[./IDENT[@text='InputXpathMapperPositions']]"
771                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='switchMethod']]"
772                 + "/SLIST/LITERAL_SWITCH/CASE_GROUP[4]";
773         final RootNode rootNode = getRootNode("InputXpathMapperPositions.java");
774         final DetailAST[] actual = convertToArray(getXpathItems(xpath, rootNode));
775         final DetailAST expectedVariableDefNode = getSiblingByType(rootNode.getUnderlyingNode(),
776                 TokenTypes.CLASS_DEF)
777                 .findFirstToken(TokenTypes.OBJBLOCK)
778                 .findFirstToken(TokenTypes.METHOD_DEF)
779                 .findFirstToken(TokenTypes.SLIST)
780                 .findFirstToken(TokenTypes.LITERAL_SWITCH)
781                 .findFirstToken(TokenTypes.CASE_GROUP)
782                 .getNextSibling()
783                 .getNextSibling()
784                 .getNextSibling();
785         final DetailAST[] expected = {expectedVariableDefNode};
786         assertArrayEquals("Result nodes differ from expected", expected, actual);
787     }
788 
789     private RootNode getRootNode(String fileName) throws Exception {
790         final File file = new File(getPath(fileName));
791         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
792         return new RootNode(rootAst);
793     }
794 
795     private static DetailAST[] convertToArray(List<NodeInfo> nodes) {
796         final DetailAST[] result = new DetailAST[nodes.size()];
797         for (int i = 0; i < nodes.size(); i++) {
798             final AbstractNode abstractNode = (AbstractNode) nodes.get(i);
799             result[i] = abstractNode.getUnderlyingNode();
800         }
801         return result;
802     }
803 
804     private static DetailAST getSiblingByType(DetailAST node, int type) {
805         DetailAST returnValue = null;
806         for (DetailAST ast = node; ast != null; ast = ast.getNextSibling()) {
807             if (ast.getType() == type) {
808                 returnValue = ast;
809                 break;
810             }
811         }
812         return returnValue;
813     }
814 
815 }