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