View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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.BufferedWriter;
29  import java.io.File;
30  import java.io.FileOutputStream;
31  import java.io.OutputStreamWriter;
32  import java.lang.reflect.Method;
33  import java.nio.charset.StandardCharsets;
34  import java.text.MessageFormat;
35  import java.util.Arrays;
36  import java.util.BitSet;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.function.Consumer;
40  
41  import org.junit.Rule;
42  import org.junit.Test;
43  import org.junit.rules.TemporaryFolder;
44  import org.powermock.reflect.Whitebox;
45  
46  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
47  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
48  import com.puppycrawl.tools.checkstyle.JavaParser;
49  import com.puppycrawl.tools.checkstyle.checks.TodoCommentCheck;
50  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
51  
52  /**
53   * TestCase to check DetailAST.
54   * @author Oliver Burn
55   */
56  public class DetailASTTest extends AbstractModuleTestSupport {
57  
58      @Rule
59      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
60  
61      @Override
62      protected String getPackageLocation() {
63          return "com/puppycrawl/tools/checkstyle/api/detailast";
64      }
65  
66      private static Method getSetParentMethod() throws Exception {
67          final Class<DetailAST> detailAstClass = DetailAST.class;
68          final Method setParentMethod =
69              detailAstClass.getDeclaredMethod("setParent", DetailAST.class);
70          setParentMethod.setAccessible(true);
71          return setParentMethod;
72      }
73  
74      @Test
75      public void testGetChildCount() throws Exception {
76          final DetailAST root = new DetailAST();
77          final DetailAST firstLevelA = new DetailAST();
78          final DetailAST firstLevelB = new DetailAST();
79          final DetailAST secondLevelA = new DetailAST();
80  
81          root.setFirstChild(firstLevelA);
82  
83          final Method setParentMethod = getSetParentMethod();
84          setParentMethod.invoke(firstLevelA, root);
85          firstLevelA.setFirstChild(secondLevelA);
86          firstLevelA.setNextSibling(firstLevelB);
87  
88          setParentMethod.invoke(firstLevelB, root);
89  
90          setParentMethod.invoke(secondLevelA, root);
91  
92          assertEquals("Invalid child count", 0, secondLevelA.getChildCount());
93          assertEquals("Invalid child count", 0, firstLevelB.getChildCount());
94          assertEquals("Invalid child count", 1, firstLevelA.getChildCount());
95          assertEquals("Invalid child count", 2, root.getChildCount());
96          assertEquals("Invalid child count", 2, root.getChildCount());
97  
98          assertNull("Previous sibling should be null", root.getPreviousSibling());
99          assertNull("Previous sibling should be null", firstLevelA.getPreviousSibling());
100         assertNull("Previous sibling should be null", secondLevelA.getPreviousSibling());
101         assertEquals("Invalid previous sibling", firstLevelA, firstLevelB.getPreviousSibling());
102     }
103 
104     @Test
105     public void testSetSiblingNull() throws Exception {
106         final DetailAST root = new DetailAST();
107         final DetailAST firstLevelA = new DetailAST();
108 
109         root.setFirstChild(firstLevelA);
110 
111         assertEquals("Invalid child count", 1, root.getChildCount());
112 
113         getSetParentMethod().invoke(firstLevelA, root);
114         firstLevelA.addPreviousSibling(null);
115         firstLevelA.addNextSibling(null);
116 
117         assertEquals("Invalid child count", 1, root.getChildCount());
118     }
119 
120     @Test
121     public void testInsertSiblingBetween() throws Exception {
122         final DetailAST root = new DetailAST();
123         final DetailAST firstLevelA = new DetailAST();
124         final DetailAST firstLevelB = new DetailAST();
125         final DetailAST firstLevelC = new DetailAST();
126 
127         assertEquals("Invalid child count", 0, root.getChildCount());
128 
129         root.setFirstChild(firstLevelA);
130         final Method setParentMethod = getSetParentMethod();
131         setParentMethod.invoke(firstLevelA, root);
132 
133         assertEquals("Invalid child count", 1, root.getChildCount());
134 
135         firstLevelA.addNextSibling(firstLevelB);
136         setParentMethod.invoke(firstLevelB, root);
137 
138         assertEquals("Invalid next sibling", firstLevelB, firstLevelA.getNextSibling());
139 
140         firstLevelA.addNextSibling(firstLevelC);
141         setParentMethod.invoke(firstLevelC, root);
142 
143         assertEquals("Invalid next sibling", firstLevelC, firstLevelA.getNextSibling());
144     }
145 
146     @Test
147     public void testBranchContains() {
148         final DetailAST root = createToken(null, TokenTypes.CLASS_DEF);
149         final DetailAST modifiers = createToken(root, TokenTypes.MODIFIERS);
150         createToken(modifiers, TokenTypes.LITERAL_PUBLIC);
151 
152         assertTrue("invalid result", root.branchContains(TokenTypes.LITERAL_PUBLIC));
153         assertFalse("invalid result", root.branchContains(TokenTypes.OBJBLOCK));
154     }
155 
156     private static DetailAST createToken(DetailAST root, int type) {
157         final DetailAST result = new DetailAST();
158         result.setType(type);
159         if (root != null) {
160             root.addChild(result);
161         }
162         return result;
163     }
164 
165     @Test
166     public void testClearBranchTokenTypes() throws Exception {
167         final DetailAST parent = new DetailAST();
168         final DetailAST child = new DetailAST();
169         parent.setFirstChild(child);
170 
171         final List<Consumer<DetailAST>> clearBranchTokenTypesMethods = Arrays.asList(
172                 child::setFirstChild,
173                 child::setNextSibling,
174                 child::addPreviousSibling,
175                 child::addNextSibling,
176                 child::addChild,
177             ast -> {
178                 try {
179                     Whitebox.invokeMethod(child, "setParent", ast);
180                 }
181                 // -@cs[IllegalCatch] Cannot avoid catching it.
182                 catch (Exception exception) {
183                     throw new IllegalStateException(exception);
184                 }
185             }
186         );
187 
188         for (Consumer<DetailAST> method : clearBranchTokenTypesMethods) {
189             final BitSet branchTokenTypes = Whitebox.invokeMethod(parent, "getBranchTokenTypes");
190             method.accept(null);
191             final BitSet branchTokenTypes2 = Whitebox.invokeMethod(parent, "getBranchTokenTypes");
192             assertEquals("Branch token types are not equal", branchTokenTypes, branchTokenTypes2);
193             assertNotSame("Branch token types should not be the same",
194                     branchTokenTypes, branchTokenTypes2);
195         }
196     }
197 
198     @Test
199     public void testClearChildCountCache() {
200         final DetailAST parent = new DetailAST();
201         final DetailAST child = new DetailAST();
202         parent.setFirstChild(child);
203 
204         final List<Consumer<DetailAST>> clearChildCountCacheMethods = Arrays.asList(
205                 child::setNextSibling,
206                 child::addPreviousSibling,
207                 child::addNextSibling
208         );
209 
210         for (Consumer<DetailAST> method : clearChildCountCacheMethods) {
211             final int startCount = parent.getChildCount();
212             method.accept(null);
213             final int intermediateCount = Whitebox.getInternalState(parent, "childCount");
214             final int finishCount = parent.getChildCount();
215             assertEquals("Child count has changed", startCount, finishCount);
216             assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount);
217         }
218 
219         final int startCount = child.getChildCount();
220         child.addChild(null);
221         final int intermediateCount = Whitebox.getInternalState(child, "childCount");
222         final int finishCount = child.getChildCount();
223         assertEquals("Child count has changed", startCount, finishCount);
224         assertEquals("Invalid child count", Integer.MIN_VALUE, intermediateCount);
225     }
226 
227     @Test
228     public void testAddNextSibling() {
229         final DetailAST parent = new DetailAST();
230         final DetailAST child = new DetailAST();
231         final DetailAST sibling = new DetailAST();
232         final DetailAST newSibling = new DetailAST();
233         parent.setFirstChild(child);
234         child.setNextSibling(sibling);
235         child.addNextSibling(newSibling);
236 
237         assertEquals("Invalid parent", parent, newSibling.getParent());
238         assertEquals("Invalid next sibling", sibling, newSibling.getNextSibling());
239         assertEquals("Invalid child", newSibling, child.getNextSibling());
240     }
241 
242     @Test
243     public void testManyComments() throws Exception {
244         final File file = temporaryFolder.newFile("InputDetailASTManyComments.java");
245 
246         try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
247                 new FileOutputStream(file), StandardCharsets.UTF_8))) {
248             bw.write("class C {\n");
249             for (int i = 0; i <= 30000; i++) {
250                 bw.write("// " + i + "\n");
251             }
252             bw.write("}\n");
253         }
254 
255         final DefaultConfiguration checkConfig = createModuleConfig(TodoCommentCheck.class);
256 
257         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
258         verify(checkConfig, file.getAbsolutePath(), expected);
259     }
260 
261     /**
262      * There are asserts in checkNode, but idea does not see them.
263      * @noinspection JUnitTestMethodWithNoAssertions
264      */
265     @Test
266     public void testTreeStructure() throws Exception {
267         checkDir(new File("src/test/resources/com/puppycrawl/tools/checkstyle"));
268     }
269 
270     @Test
271     public void testToString() {
272         final DetailAST ast = new DetailAST();
273         ast.setText("text");
274         ast.setColumnNo(0);
275         ast.setLineNo(0);
276         assertEquals("Invalid text", "text[0x0]", ast.toString());
277     }
278 
279     private static void checkDir(File dir) throws Exception {
280         final File[] files = dir.listFiles(file -> {
281             return (file.getName().endsWith(".java")
282                 || file.isDirectory())
283                 && !file.getName().endsWith("InputGrammar.java");
284         });
285         for (File file : files) {
286             if (file.isFile()) {
287                 checkFile(file.getCanonicalPath());
288             }
289             else if (file.isDirectory()) {
290                 checkDir(file);
291             }
292         }
293     }
294 
295     private static void checkFile(String filename) throws Exception {
296         final DetailAST rootAST =
297             JavaParser.parseFile(new File(filename), JavaParser.Options.WITHOUT_COMMENTS);
298         if (rootAST != null) {
299             checkTree(filename, rootAST);
300         }
301     }
302 
303     private static void checkTree(final String filename, final DetailAST root) {
304         DetailAST curNode = root;
305         DetailAST parent = null;
306         DetailAST prev = null;
307         while (curNode != null) {
308             checkNode(curNode, parent, prev, filename, root);
309             DetailAST toVisit = curNode.getFirstChild();
310             if (toVisit == null) {
311                 while (curNode != null && toVisit == null) {
312                     toVisit = curNode.getNextSibling();
313                     if (toVisit == null) {
314                         curNode = curNode.getParent();
315                         if (curNode != null) {
316                             parent = curNode.getParent();
317                         }
318                     }
319                     else {
320                         prev = curNode;
321                         curNode = toVisit;
322                     }
323                 }
324             }
325             else {
326                 parent = curNode;
327                 curNode = toVisit;
328                 prev = null;
329             }
330         }
331     }
332 
333     private static void checkNode(final DetailAST node,
334                                   final DetailAST parent,
335                                   final DetailAST prev,
336                                   final String filename,
337                                   final DetailAST root) {
338         final Object[] params = {
339             node, parent, prev, filename, root,
340         };
341         final MessageFormat badParentFormatter = new MessageFormat(
342                 "Bad parent node={0} parent={1} filename={3} root={4}", Locale.ROOT);
343         final String badParentMsg = badParentFormatter.format(params);
344         assertEquals(badParentMsg, parent, node.getParent());
345         final MessageFormat badPrevFormatter = new MessageFormat(
346                 "Bad prev node={0} prev={2} parent={1} filename={3} root={4}", Locale.ROOT);
347         final String badPrevMsg = badPrevFormatter.format(params);
348         assertEquals(badPrevMsg, prev, node.getPreviousSibling());
349     }
350 
351 }