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.api;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotSame;
25  import static org.junit.Assert.assertNull;
26  import static org.junit.Assert.assertTrue;
27  
28  import java.io.File;
29  import java.io.Writer;
30  import java.lang.reflect.Method;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.text.MessageFormat;
34  import java.util.Arrays;
35  import java.util.BitSet;
36  import java.util.List;
37  import java.util.Locale;
38  import java.util.function.Consumer;
39  
40  import org.junit.Rule;
41  import org.junit.Test;
42  import org.junit.rules.TemporaryFolder;
43  import org.powermock.reflect.Whitebox;
44  
45  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
46  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
47  import com.puppycrawl.tools.checkstyle.JavaParser;
48  import com.puppycrawl.tools.checkstyle.checks.TodoCommentCheck;
49  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
50  
51  /**
52   * TestCase to check DetailAST.
53   */
54  public class DetailASTTest extends AbstractModuleTestSupport {
55  
56      @Rule
57      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
58  
59      @Override
60      protected String getPackageLocation() {
61          return "com/puppycrawl/tools/checkstyle/api/detailast";
62      }
63  
64      private static Method getSetParentMethod() throws Exception {
65          final Class<DetailAST> detailAstClass = DetailAST.class;
66          final Method setParentMethod =
67              detailAstClass.getDeclaredMethod("setParent", DetailAST.class);
68          setParentMethod.setAccessible(true);
69          return setParentMethod;
70      }
71  
72      @Test
73      public void testGetChildCount() throws Exception {
74          final DetailAST root = new DetailAST();
75          final DetailAST firstLevelA = new DetailAST();
76          final DetailAST firstLevelB = new DetailAST();
77          final DetailAST secondLevelA = new DetailAST();
78  
79          root.setFirstChild(firstLevelA);
80  
81          final Method setParentMethod = getSetParentMethod();
82          setParentMethod.invoke(firstLevelA, root);
83          firstLevelA.setFirstChild(secondLevelA);
84          firstLevelA.setNextSibling(firstLevelB);
85  
86          setParentMethod.invoke(firstLevelB, root);
87  
88          setParentMethod.invoke(secondLevelA, root);
89  
90          assertEquals("Invalid child count", 0, secondLevelA.getChildCount());
91          assertEquals("Invalid child count", 0, firstLevelB.getChildCount());
92          assertEquals("Invalid child count", 1, firstLevelA.getChildCount());
93          assertEquals("Invalid child count", 2, root.getChildCount());
94          assertEquals("Invalid child count", 2, root.getChildCount());
95  
96          assertNull("Previous sibling should be null", root.getPreviousSibling());
97          assertNull("Previous sibling should be null", firstLevelA.getPreviousSibling());
98          assertNull("Previous sibling should be null", secondLevelA.getPreviousSibling());
99          assertEquals("Invalid previous sibling", firstLevelA, firstLevelB.getPreviousSibling());
100     }
101 
102     @Test
103     public void testGetChildCountType() throws Exception {
104         final DetailAST root = new DetailAST();
105         final DetailAST firstLevelA = new DetailAST();
106         final DetailAST firstLevelB = new DetailAST();
107 
108         root.setFirstChild(firstLevelA);
109 
110         final Method setParentMethod = getSetParentMethod();
111         setParentMethod.invoke(firstLevelA, root);
112         firstLevelA.setNextSibling(firstLevelB);
113 
114         firstLevelA.setType(TokenTypes.IDENT);
115         firstLevelB.setType(TokenTypes.EXPR);
116 
117         setParentMethod.invoke(firstLevelB, root);
118 
119         assertEquals("Invalid child count", 0, firstLevelB.getChildCount(0));
120         assertEquals("Invalid child count", 0, firstLevelA.getChildCount(TokenTypes.EXPR));
121         assertEquals("Invalid child count", 1, root.getChildCount(TokenTypes.IDENT));
122         assertEquals("Invalid child count", 1, root.getChildCount(TokenTypes.EXPR));
123         assertEquals("Invalid child count", 0, root.getChildCount(0));
124     }
125 
126     @Test
127     public void testSetSiblingNull() throws Exception {
128         final DetailAST root = new DetailAST();
129         final DetailAST firstLevelA = new DetailAST();
130 
131         root.setFirstChild(firstLevelA);
132 
133         assertEquals("Invalid child count", 1, root.getChildCount());
134 
135         getSetParentMethod().invoke(firstLevelA, root);
136         firstLevelA.addPreviousSibling(null);
137         firstLevelA.addNextSibling(null);
138 
139         assertEquals("Invalid child count", 1, root.getChildCount());
140     }
141 
142     @Test
143     public void testAddPreviousSibling() {
144         final DetailAST previousSibling = new DetailAST();
145         final DetailAST instance = new DetailAST();
146         final DetailAST parent = new DetailAST();
147 
148         parent.setFirstChild(instance);
149 
150         instance.addPreviousSibling(previousSibling);
151 
152         assertEquals("unexpected result", previousSibling, instance.getPreviousSibling());
153         assertEquals("unexpected result", previousSibling, parent.getFirstChild());
154 
155         final DetailAST newPreviousSibling = new DetailAST();
156 
157         instance.addPreviousSibling(newPreviousSibling);
158 
159         assertEquals("unexpected result", newPreviousSibling, instance.getPreviousSibling());
160         assertEquals("unexpected result", previousSibling, newPreviousSibling.getPreviousSibling());
161         assertEquals("unexpected result", newPreviousSibling, previousSibling.getNextSibling());
162         assertEquals("unexpected result", previousSibling, parent.getFirstChild());
163     }
164 
165     @Test
166     public void testAddPreviousSiblingNullParent() {
167         final DetailAST child = new DetailAST();
168         final DetailAST newSibling = new DetailAST();
169 
170         child.addPreviousSibling(newSibling);
171 
172         assertEquals("Invalid child token", child, newSibling.getNextSibling());
173         assertEquals("Invalid child token", newSibling, child.getPreviousSibling());
174     }
175 
176     @Test
177     public void testInsertSiblingBetween() throws Exception {
178         final DetailAST root = new DetailAST();
179         final DetailAST firstLevelA = new DetailAST();
180         final DetailAST firstLevelB = new DetailAST();
181         final DetailAST firstLevelC = new DetailAST();
182 
183         assertEquals("Invalid child count", 0, root.getChildCount());
184 
185         root.setFirstChild(firstLevelA);
186         final Method setParentMethod = getSetParentMethod();
187         setParentMethod.invoke(firstLevelA, root);
188 
189         assertEquals("Invalid child count", 1, root.getChildCount());
190 
191         firstLevelA.addNextSibling(firstLevelB);
192         setParentMethod.invoke(firstLevelB, root);
193 
194         assertEquals("Invalid next sibling", firstLevelB, firstLevelA.getNextSibling());
195 
196         firstLevelA.addNextSibling(firstLevelC);
197         setParentMethod.invoke(firstLevelC, root);
198 
199         assertEquals("Invalid next sibling", firstLevelC, firstLevelA.getNextSibling());
200     }
201 
202     @Test
203     public void testBranchContains() {
204         final DetailAST root = createToken(null, TokenTypes.CLASS_DEF);
205         final DetailAST modifiers = createToken(root, TokenTypes.MODIFIERS);
206         createToken(modifiers, TokenTypes.LITERAL_PUBLIC);
207 
208         assertTrue("invalid result", root.branchContains(TokenTypes.LITERAL_PUBLIC));
209         assertFalse("invalid result", root.branchContains(TokenTypes.OBJBLOCK));
210     }
211 
212     private static DetailAST createToken(DetailAST root, int type) {
213         final DetailAST result = new DetailAST();
214         result.setType(type);
215         if (root != null) {
216             root.addChild(result);
217         }
218         return result;
219     }
220 
221     @Test
222     public void testClearBranchTokenTypes() throws Exception {
223         final DetailAST parent = new DetailAST();
224         final DetailAST child = new DetailAST();
225         parent.setFirstChild(child);
226 
227         final List<Consumer<DetailAST>> clearBranchTokenTypesMethods = Arrays.asList(
228                 child::setFirstChild,
229                 child::setNextSibling,
230                 child::addPreviousSibling,
231                 child::addNextSibling,
232                 child::addChild,
233             ast -> {
234                 try {
235                     Whitebox.invokeMethod(child, "setParent", ast);
236                 }
237                 // -@cs[IllegalCatch] Cannot avoid catching it.
238                 catch (Exception exception) {
239                     throw new IllegalStateException(exception);
240                 }
241             }
242         );
243 
244         for (Consumer<DetailAST> method : clearBranchTokenTypesMethods) {
245             final BitSet branchTokenTypes = Whitebox.invokeMethod(parent, "getBranchTokenTypes");
246             method.accept(null);
247             final BitSet branchTokenTypes2 = Whitebox.invokeMethod(parent, "getBranchTokenTypes");
248             assertEquals("Branch token types are not equal", branchTokenTypes, branchTokenTypes2);
249             assertNotSame("Branch token types should not be the same",
250                     branchTokenTypes, branchTokenTypes2);
251         }
252     }
253 
254     @Test
255     public void testCacheBranchTokenTypes() {
256         final DetailAST root = new DetailAST();
257         final BitSet bitSet = new BitSet();
258         bitSet.set(999);
259 
260         Whitebox.setInternalState(root, "branchTokenTypes", bitSet);
261         assertTrue("Branch tokens has changed", root.branchContains(999));
262     }
263 
264     @Test
265     public void testClearChildCountCache() {
266         final DetailAST parent = new DetailAST();
267         final DetailAST child = new DetailAST();
268         parent.setFirstChild(child);
269 
270         final List<Consumer<DetailAST>> clearChildCountCacheMethods = Arrays.asList(
271                 child::setNextSibling,
272                 child::addPreviousSibling,
273                 child::addNextSibling
274         );
275 
276         for (Consumer<DetailAST> method : clearChildCountCacheMethods) {
277             final int startCount = parent.getChildCount();
278             method.accept(null);
279             final int intermediateCount = Whitebox.getInternalState(parent, "childCount");
280             final int finishCount = parent.getChildCount();
281             assertEquals("Child count has changed", startCount, finishCount);
282             assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount);
283         }
284 
285         final int startCount = child.getChildCount();
286         child.addChild(null);
287         final int intermediateCount = Whitebox.getInternalState(child, "childCount");
288         final int finishCount = child.getChildCount();
289         assertEquals("Child count has changed", startCount, finishCount);
290         assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount);
291     }
292 
293     @Test
294     public void testCacheGetChildCount() {
295         final DetailAST root = new DetailAST();
296 
297         Whitebox.setInternalState(root, "childCount", 999);
298         assertEquals("Child count has changed", 999, root.getChildCount());
299     }
300 
301     @Test
302     public void testAddNextSibling() {
303         final DetailAST parent = new DetailAST();
304         final DetailAST child = new DetailAST();
305         final DetailAST sibling = new DetailAST();
306         final DetailAST newSibling = new DetailAST();
307         parent.setFirstChild(child);
308         child.setNextSibling(sibling);
309         child.addNextSibling(newSibling);
310 
311         assertEquals("Invalid parent", parent, newSibling.getParent());
312         assertEquals("Invalid next sibling", sibling, newSibling.getNextSibling());
313         assertEquals("Invalid child", newSibling, child.getNextSibling());
314     }
315 
316     @Test
317     public void testAddNextSiblingNullParent() {
318         final DetailAST child = new DetailAST();
319         final DetailAST newSibling = new DetailAST();
320         final DetailAST oldParent = new DetailAST();
321         oldParent.addChild(newSibling);
322         child.addNextSibling(newSibling);
323 
324         assertEquals("Invalid parent", oldParent, newSibling.getParent());
325         assertNull("Invalid next sibling", newSibling.getNextSibling());
326         assertEquals("Invalid child", newSibling, child.getNextSibling());
327     }
328 
329     @Test
330     public void testGetLineNo() {
331         final DetailAST root1 = new DetailAST();
332         root1.setLineNo(1);
333         assertEquals("Invalid line number", 1, root1.getLineNo());
334 
335         final DetailAST root2 = new DetailAST();
336         final DetailAST firstChild = new DetailAST();
337         firstChild.setLineNo(2);
338         root2.setFirstChild(firstChild);
339         assertEquals("Invalid line number", 2, root2.getLineNo());
340 
341         final DetailAST root3 = new DetailAST();
342         final DetailAST nextSibling = new DetailAST();
343         nextSibling.setLineNo(3);
344         root3.setNextSibling(nextSibling);
345         assertEquals("Invalid line number", 3, root3.getLineNo());
346 
347         final DetailAST root4 = new DetailAST();
348         final DetailAST comment = new DetailAST();
349         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
350         comment.setLineNo(3);
351         root4.setFirstChild(comment);
352         assertEquals("Invalid line number", Integer.MIN_VALUE, root4.getLineNo());
353     }
354 
355     @Test
356     public void testGetColumnNo() {
357         final DetailAST root1 = new DetailAST();
358         root1.setColumnNo(1);
359         assertEquals("Invalid column number", 1, root1.getColumnNo());
360 
361         final DetailAST root2 = new DetailAST();
362         final DetailAST firstChild = new DetailAST();
363         firstChild.setColumnNo(2);
364         root2.setFirstChild(firstChild);
365         assertEquals("Invalid column number", 2, root2.getColumnNo());
366 
367         final DetailAST root3 = new DetailAST();
368         final DetailAST nextSibling = new DetailAST();
369         nextSibling.setColumnNo(3);
370         root3.setNextSibling(nextSibling);
371         assertEquals("Invalid column number", 3, root3.getColumnNo());
372 
373         final DetailAST root4 = new DetailAST();
374         final DetailAST comment = new DetailAST();
375         comment.setType(TokenTypes.SINGLE_LINE_COMMENT);
376         comment.setColumnNo(3);
377         root4.setFirstChild(comment);
378         assertEquals("Invalid column number", Integer.MIN_VALUE, root4.getColumnNo());
379     }
380 
381     @Test
382     public void testFindFirstToken() {
383         final DetailAST root = new DetailAST();
384         final DetailAST firstChild = new DetailAST();
385         firstChild.setType(TokenTypes.IDENT);
386         final DetailAST secondChild = new DetailAST();
387         secondChild.setType(TokenTypes.EXPR);
388         final DetailAST thirdChild = new DetailAST();
389         thirdChild.setType(TokenTypes.IDENT);
390 
391         root.addChild(firstChild);
392         root.addChild(secondChild);
393         root.addChild(thirdChild);
394 
395         assertNull("Invalid result", firstChild.findFirstToken(TokenTypes.IDENT));
396         assertEquals("Invalid result", firstChild, root.findFirstToken(TokenTypes.IDENT));
397         assertEquals("Invalid result", secondChild, root.findFirstToken(TokenTypes.EXPR));
398         assertNull("Invalid result", root.findFirstToken(0));
399     }
400 
401     @Test
402     public void testManyComments() throws Exception {
403         final File file = temporaryFolder.newFile("InputDetailASTManyComments.java");
404 
405         try (Writer bw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
406             bw.write("class C {\n");
407             for (int i = 0; i <= 30000; i++) {
408                 bw.write("// " + i + "\n");
409             }
410             bw.write("}\n");
411         }
412 
413         final DefaultConfiguration checkConfig = createModuleConfig(TodoCommentCheck.class);
414 
415         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
416         verify(checkConfig, file.getAbsolutePath(), expected);
417     }
418 
419     /**
420      * There are asserts in checkNode, but idea does not see them.
421      * @noinspection JUnitTestMethodWithNoAssertions
422      */
423     @Test
424     public void testTreeStructure() throws Exception {
425         checkDir(new File("src/test/resources/com/puppycrawl/tools/checkstyle"));
426     }
427 
428     @Test
429     public void testToString() {
430         final DetailAST ast = new DetailAST();
431         ast.setText("text");
432         ast.setColumnNo(0);
433         ast.setLineNo(0);
434         assertEquals("Invalid text", "text[0x0]", ast.toString());
435     }
436 
437     private static void checkDir(File dir) throws Exception {
438         final File[] files = dir.listFiles(file -> {
439             return (file.getName().endsWith(".java")
440                 || file.isDirectory())
441                 && !file.getName().endsWith("InputGrammar.java");
442         });
443         for (File file : files) {
444             if (file.isFile()) {
445                 checkFile(file.getCanonicalPath());
446             }
447             else if (file.isDirectory()) {
448                 checkDir(file);
449             }
450         }
451     }
452 
453     private static void checkFile(String filename) throws Exception {
454         final DetailAST rootAST =
455             JavaParser.parseFile(new File(filename), JavaParser.Options.WITHOUT_COMMENTS);
456         if (rootAST != null) {
457             checkTree(filename, rootAST);
458         }
459     }
460 
461     private static void checkTree(final String filename, final DetailAST root) {
462         DetailAST curNode = root;
463         DetailAST parent = null;
464         DetailAST prev = null;
465         while (curNode != null) {
466             checkNode(curNode, parent, prev, filename, root);
467             DetailAST toVisit = curNode.getFirstChild();
468             if (toVisit == null) {
469                 while (curNode != null && toVisit == null) {
470                     toVisit = curNode.getNextSibling();
471                     if (toVisit == null) {
472                         curNode = curNode.getParent();
473                         if (curNode != null) {
474                             parent = curNode.getParent();
475                         }
476                     }
477                     else {
478                         prev = curNode;
479                         curNode = toVisit;
480                     }
481                 }
482             }
483             else {
484                 parent = curNode;
485                 curNode = toVisit;
486                 prev = null;
487             }
488         }
489     }
490 
491     private static void checkNode(final DetailAST node,
492                                   final DetailAST parent,
493                                   final DetailAST prev,
494                                   final String filename,
495                                   final DetailAST root) {
496         final Object[] params = {
497             node, parent, prev, filename, root,
498         };
499         final MessageFormat badParentFormatter = new MessageFormat(
500                 "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT);
501         final String badParentMsg = badParentFormatter.format(params);
502         assertEquals(badParentMsg, parent, node.getParent());
503         final MessageFormat badPrevFormatter = new MessageFormat(
504                 "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT);
505         final String badPrevMsg = badPrevFormatter.format(params);
506         assertEquals(badPrevMsg, prev, node.getPreviousSibling());
507     }
508 
509 }