View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.utils;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;
24  
25  import java.lang.reflect.Field;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.TreeMap;
32  
33  import org.junit.jupiter.api.Test;
34  
35  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
36  import com.puppycrawl.tools.checkstyle.api.DetailAST;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  
39  public class TokenUtilTest {
40  
41      @Test
42      public void testIsProperUtilsClass() throws ReflectiveOperationException {
43          assertWithMessage("Constructor is not private")
44                  .that(isUtilsClassHasPrivateConstructor(TokenUtil.class))
45                  .isTrue();
46      }
47  
48      @Test
49      public void testGetIntFromAccessibleField() throws NoSuchFieldException {
50          final Field field = Integer.class.getField("MAX_VALUE");
51          final int maxValue = TokenUtil.getIntFromField(field, 0);
52  
53          assertWithMessage("Invalid getIntFromField result")
54              .that(maxValue)
55              .isEqualTo(Integer.MAX_VALUE);
56      }
57  
58      @Test
59      public void testGetIntFromInaccessibleField() throws NoSuchFieldException {
60          final Field field = Integer.class.getDeclaredField("value");
61  
62          try {
63              TokenUtil.getIntFromField(field, 0);
64              assertWithMessage("IllegalStateException is expected").fail();
65          }
66          catch (IllegalStateException expected) {
67              // The exception message may vary depending on the version of the JDK.
68              // It will definitely contain the TokenUtil class name and the Integer class name.
69              final String message = expected.getMessage();
70              assertWithMessage("Invalid exception message: " + message)
71                      .that(message.startsWith("java.lang.IllegalAccessException: ")
72                              && message.contains("com.puppycrawl.tools.checkstyle.utils.TokenUtil")
73                              && message.contains("access a member of class java.lang.Integer"))
74                      .isTrue();
75          }
76      }
77  
78      @Test
79      public void testNameToValueMapFromPublicIntFields() {
80          final Map<String, Integer> actualMap =
81              TokenUtil.nameToValueMapFromPublicIntFields(Integer.class);
82          final Map<String, Integer> expectedMap = new TreeMap<>();
83          expectedMap.put("BYTES", Integer.BYTES);
84          expectedMap.put("SIZE", Integer.SIZE);
85          expectedMap.put("MAX_VALUE", Integer.MAX_VALUE);
86          expectedMap.put("MIN_VALUE", Integer.MIN_VALUE);
87  
88          assertWithMessage("Unexpected name to value map")
89              .that(actualMap)
90              .isEqualTo(expectedMap);
91      }
92  
93      @Test
94      public void testInvertMap() {
95          final Map<String, Integer> map = new TreeMap<>();
96          map.put("ZERO", 0);
97          map.put("ONE", 1);
98          map.put("TWO", 2);
99          map.put("NEGATIVE", -1);
100 
101         final Map<Integer, String> invertedMap = TokenUtil.invertMap(map);
102 
103         assertWithMessage("Key set of 'map' and values of 'invertedMap' should be the same.")
104                 .that(invertedMap.values())
105                 .containsExactlyElementsIn(map.keySet());
106         assertWithMessage("Values of 'map' and key set of 'invertedMap' should be the same.")
107                 .that(invertedMap.keySet())
108                 .containsExactlyElementsIn(map.values());
109     }
110 
111     @Test
112     public void testTokenValueIncorrect() throws IllegalAccessException {
113         int maxId = 0;
114         final Field[] fields = TokenTypes.class.getDeclaredFields();
115         for (final Field field : fields) {
116             // Only process the int declarations.
117             if (field.getType() != Integer.TYPE) {
118                 continue;
119             }
120 
121             final String name = field.getName();
122             final int id = field.getInt(name);
123             if (id > maxId) {
124                 maxId = id;
125             }
126         }
127 
128         final int nextAfterMaxId = maxId + 1;
129         try {
130             TokenUtil.getTokenName(nextAfterMaxId);
131             assertWithMessage("IllegalArgumentException is expected").fail();
132         }
133         catch (IllegalArgumentException expectedException) {
134             assertWithMessage("Invalid exception message")
135                 .that(expectedException.getMessage())
136                 .isEqualTo("unknown TokenTypes id '" + nextAfterMaxId + "'");
137         }
138     }
139 
140     @Test
141     public void testTokenValueCorrect() throws IllegalAccessException {
142         final Field[] fields = TokenTypes.class.getDeclaredFields();
143         for (final Field field : fields) {
144             // Only process the int declarations.
145             if (field.getType() != Integer.TYPE) {
146                 continue;
147             }
148 
149             final String name = field.getName();
150             final int id = field.getInt(name);
151 
152             assertWithMessage("Invalid token name")
153                 .that(TokenUtil.getTokenName(id))
154                 .isEqualTo(name);
155         }
156     }
157 
158     @Test
159     public void testTokenValueIncorrect2() {
160         final int id = 0;
161         try {
162             TokenUtil.getTokenName(id);
163             assertWithMessage("IllegalArgumentException is expected").fail();
164         }
165         catch (IllegalArgumentException expected) {
166             assertWithMessage("Invalid exception message")
167                 .that(expected.getMessage())
168                 .isEqualTo("unknown TokenTypes id '" + id + "'");
169         }
170     }
171 
172     @Test
173     public void testTokenIdIncorrect() {
174         final String id = "NON_EXISTENT_VALUE";
175         try {
176             TokenUtil.getTokenId(id);
177             assertWithMessage("IllegalArgumentException is expected").fail();
178         }
179         catch (IllegalArgumentException expected) {
180             assertWithMessage("Invalid exception message")
181                 .that(expected.getMessage())
182                 .isEqualTo("unknown TokenTypes value '" + id + "'");
183         }
184     }
185 
186     @Test
187     public void testShortDescriptionIncorrect() {
188         final String id = "NON_EXISTENT_VALUE";
189         try {
190             TokenUtil.getShortDescription(id);
191             assertWithMessage("IllegalArgumentException is expected").fail();
192         }
193         catch (IllegalArgumentException expected) {
194             assertWithMessage("Invalid exception message")
195                 .that(expected.getMessage())
196                 .isEqualTo("unknown TokenTypes value '" + id + "'");
197         }
198     }
199 
200     @Test
201     public void testIsCommentType() {
202         assertWithMessage("Should return true when valid type passed")
203                 .that(TokenUtil.isCommentType(TokenTypes.SINGLE_LINE_COMMENT))
204                 .isTrue();
205         assertWithMessage("Should return true when valid type passed")
206                 .that(TokenUtil.isCommentType(TokenTypes.BLOCK_COMMENT_BEGIN))
207                 .isTrue();
208         assertWithMessage("Should return true when valid type passed")
209                 .that(TokenUtil.isCommentType(TokenTypes.BLOCK_COMMENT_END))
210                 .isTrue();
211         assertWithMessage("Should return true when valid type passed")
212                 .that(TokenUtil.isCommentType(TokenTypes.COMMENT_CONTENT))
213                 .isTrue();
214         assertWithMessage("Should return true when valid type passed")
215                 .that(TokenUtil.isCommentType("COMMENT_CONTENT"))
216                 .isTrue();
217         assertWithMessage("Should return false when invalid type passed")
218                 .that(TokenUtil.isCommentType(TokenTypes.CLASS_DEF))
219                 .isFalse();
220         assertWithMessage("Should return false when invalid type passed")
221                 .that(TokenUtil.isCommentType("CLASS_DEF"))
222                 .isFalse();
223     }
224 
225     @Test
226     public void testGetTokenTypesTotalNumber() {
227         final int tokenTypesTotalNumber = TokenUtil.getTokenTypesTotalNumber();
228 
229         assertWithMessage("Invalid token total number")
230             .that(tokenTypesTotalNumber)
231             .isEqualTo(189);
232     }
233 
234     @Test
235     public void testGetAllTokenIds() {
236         final int[] allTokenIds = TokenUtil.getAllTokenIds();
237         final int sum = Arrays.stream(allTokenIds).sum();
238 
239         assertWithMessage("Invalid token length")
240             .that(allTokenIds.length)
241             .isEqualTo(189);
242         assertWithMessage("invalid sum")
243             .that(sum)
244             .isEqualTo(19820);
245     }
246 
247     @Test
248     public void testGetTokenNameWithGreatestPossibleId() {
249         final int id = TokenTypes.COMMENT_CONTENT;
250         final String tokenName = TokenUtil.getTokenName(id);
251 
252         assertWithMessage("Invalid token name")
253             .that(tokenName)
254             .isEqualTo("COMMENT_CONTENT");
255     }
256 
257     @Test
258     public void testCorrectBehaviourOfGetTokenId() {
259         final String id = "COMPILATION_UNIT";
260 
261         assertWithMessage("Invalid token id")
262             .that(TokenUtil.getTokenId(id))
263             .isEqualTo(TokenTypes.COMPILATION_UNIT);
264     }
265 
266     @Test
267     public void testCorrectBehaviourOfShortDescription() {
268         final String id = "COMPILATION_UNIT";
269         final String shortDescription = TokenUtil.getShortDescription(id);
270 
271         assertWithMessage("Invalid short description")
272             .that(shortDescription)
273             .isEqualTo("This is the root node for the source file.");
274     }
275 
276     @Test
277     public void testFindFirstTokenByPredicate() {
278         final DetailAstImpl astForTest = new DetailAstImpl();
279         final DetailAstImpl child = new DetailAstImpl();
280         final DetailAstImpl firstSibling = new DetailAstImpl();
281         final DetailAstImpl secondSibling = new DetailAstImpl();
282         final DetailAstImpl thirdSibling = new DetailAstImpl();
283         firstSibling.setText("first");
284         secondSibling.setText("second");
285         thirdSibling.setText("third");
286         secondSibling.setNextSibling(thirdSibling);
287         firstSibling.setNextSibling(secondSibling);
288         child.setNextSibling(firstSibling);
289         astForTest.setFirstChild(child);
290         final Optional<DetailAST> result = TokenUtil.findFirstTokenByPredicate(astForTest,
291             ast -> "second".equals(ast.getText()));
292 
293         assertWithMessage("Invalid second sibling")
294             .that(result.orElse(null))
295             .isEqualTo(secondSibling);
296     }
297 
298     @Test
299     public void testForEachChild() {
300         final DetailAstImpl astForTest = new DetailAstImpl();
301         final DetailAstImpl child = new DetailAstImpl();
302         final DetailAstImpl firstSibling = new DetailAstImpl();
303         final DetailAstImpl secondSibling = new DetailAstImpl();
304         final DetailAstImpl thirdSibling = new DetailAstImpl();
305         firstSibling.setType(TokenTypes.DOT);
306         secondSibling.setType(TokenTypes.CLASS_DEF);
307         thirdSibling.setType(TokenTypes.IDENT);
308         secondSibling.setNextSibling(thirdSibling);
309         firstSibling.setNextSibling(secondSibling);
310         child.setNextSibling(firstSibling);
311         astForTest.setFirstChild(child);
312         final List<DetailAST> children = new ArrayList<>();
313         TokenUtil.forEachChild(astForTest, TokenTypes.CLASS_DEF, children::add);
314         final DetailAST firstChild = children.get(0);
315 
316         assertWithMessage("Must be one match")
317             .that(children)
318             .hasSize(1);
319         assertWithMessage("Mismatched child node")
320             .that(firstChild)
321             .isEqualTo(secondSibling);
322     }
323 
324     @Test
325     public void testIsTypeDeclaration() {
326         assertWithMessage("Should return true when valid type passed")
327                 .that(TokenUtil.isTypeDeclaration(TokenTypes.CLASS_DEF))
328                 .isTrue();
329         assertWithMessage("Should return true when valid type passed")
330                 .that(TokenUtil.isTypeDeclaration(TokenTypes.INTERFACE_DEF))
331                 .isTrue();
332         assertWithMessage("Should return true when valid type passed")
333                 .that(TokenUtil.isTypeDeclaration(TokenTypes.ANNOTATION_DEF))
334                 .isTrue();
335         assertWithMessage("Should return true when valid type passed")
336                 .that(TokenUtil.isTypeDeclaration(TokenTypes.ENUM_DEF))
337                 .isTrue();
338         assertWithMessage("Should return true when valid type passed")
339                 .that(TokenUtil.isTypeDeclaration(TokenTypes.RECORD_DEF))
340                 .isTrue();
341     }
342 
343     @Test
344     public void testIsOfTypeTrue() {
345         final int type = TokenTypes.LITERAL_CATCH;
346         final DetailAstImpl astForTest = new DetailAstImpl();
347         astForTest.setType(type);
348         final boolean result1 = TokenUtil.isOfType(type, TokenTypes.LITERAL_FOR,
349                                 TokenTypes.LITERAL_IF, TokenTypes.LITERAL_CATCH);
350         final boolean result2 = TokenUtil.isOfType(astForTest, TokenTypes.LITERAL_FOR,
351                                 TokenTypes.LITERAL_IF, TokenTypes.LITERAL_CATCH);
352 
353         assertWithMessage("Token type did not match")
354                 .that(result1)
355                 .isTrue();
356         assertWithMessage("Token type did not match")
357                 .that(result2)
358                 .isTrue();
359     }
360 
361     @Test
362     public void testIsOfTypeFalse() {
363         final int type = TokenTypes.LITERAL_CATCH;
364         final DetailAstImpl astForTest1 = new DetailAstImpl();
365         final DetailAstImpl astForTest2 = null;
366         astForTest1.setType(type);
367         final boolean result1 = TokenUtil.isOfType(type, TokenTypes.LITERAL_FOR,
368                                 TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE);
369         final boolean result2 = TokenUtil.isOfType(astForTest1, TokenTypes.LITERAL_FOR,
370                                 TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE);
371         final boolean result3 = TokenUtil.isOfType(astForTest2, TokenTypes.LITERAL_FOR,
372                                 TokenTypes.LITERAL_IF, TokenTypes.LITERAL_ELSE);
373 
374         assertWithMessage("Token type should not match")
375                 .that(result1)
376                 .isFalse();
377         assertWithMessage("Token type should not match")
378                 .that(result2)
379                 .isFalse();
380         assertWithMessage("Token type should not match")
381                 .that(result3)
382                 .isFalse();
383     }
384 
385     @Test
386     public void testIsBooleanLiteralType() {
387         assertWithMessage("Result is not expected")
388                 .that(TokenUtil.isBooleanLiteralType(TokenTypes.LITERAL_TRUE))
389                 .isTrue();
390         assertWithMessage("Result is not expected")
391                 .that(TokenUtil.isBooleanLiteralType(TokenTypes.LITERAL_FALSE))
392                 .isTrue();
393         assertWithMessage("Result is not expected")
394                 .that(TokenUtil.isBooleanLiteralType(TokenTypes.LOR))
395                 .isFalse();
396     }
397 
398 }