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.internal.utils;
21  
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Modifier;
26  import java.util.Arrays;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.function.Predicate;
30  
31  import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
32  import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
33  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
34  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
35  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
36  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
37  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
38  import com.puppycrawl.tools.checkstyle.api.DetailAST;
39  
40  public final class TestUtil {
41  
42      private TestUtil() {
43      }
44  
45      /**
46       * Verifies that utils class has private constructor and invokes it to satisfy code coverage.
47       * @param utilClass class to test for c-tor
48       * @param checkConstructorIsPrivate flag to skip check for private visibility, it is useful
49       *                                  for Classes that are mocked by PowerMockRunner that make
50       *                                  private c-tors as public
51       * @return true if constructor is expected.
52       * @noinspection BooleanParameter
53       */
54      public static boolean isUtilsClassHasPrivateConstructor(final Class<?> utilClass,
55                                                               boolean checkConstructorIsPrivate)
56              throws ReflectiveOperationException {
57          final Constructor<?> constructor = utilClass.getDeclaredConstructor();
58          final boolean result;
59          if (checkConstructorIsPrivate && !Modifier.isPrivate(constructor.getModifiers())) {
60              result = false;
61          }
62          else {
63              constructor.setAccessible(true);
64              constructor.newInstance();
65              result = true;
66          }
67          return result;
68      }
69  
70      /**
71       * Retrieves the specified field by it's name in the class or it's direct super.
72       *
73       * @param clss The class to retrieve the field for.
74       * @param fieldName The name of the field to retrieve.
75       * @return The class' field.
76       * @throws NoSuchFieldException if the requested field cannot be found in the class.
77       */
78      public static Field getClassDeclaredField(Class<?> clss, String fieldName)
79              throws NoSuchFieldException {
80          final Optional<Field> classField = Arrays.stream(clss.getDeclaredFields())
81                  .filter(field -> fieldName.equals(field.getName())).findFirst();
82          final Field resultField;
83          if (classField.isPresent()) {
84              resultField = classField.get();
85          }
86          else {
87              resultField = clss.getSuperclass().getDeclaredField(fieldName);
88          }
89          resultField.setAccessible(true);
90          return resultField;
91      }
92  
93      /**
94       * Retrieves the specified method by it's name in the class or it's direct super.
95       *
96       * @param clss The class to retrieve the field for.
97       * @param methodName The name of the method to retrieve.
98       * @return The class' field.
99       * @throws NoSuchMethodException if the requested method cannot be found in the class.
100      */
101     public static Method getClassDeclaredMethod(Class<?> clss, String methodName)
102             throws NoSuchMethodException {
103         final Optional<Method> classMethod = Arrays.stream(clss.getDeclaredMethods())
104                 .filter(method -> methodName.equals(method.getName())).findFirst();
105         final Method resultMethod;
106         if (classMethod.isPresent()) {
107             resultMethod = classMethod.get();
108         }
109         else {
110             resultMethod = clss.getSuperclass().getDeclaredMethod(methodName);
111         }
112         resultMethod.setAccessible(true);
113         return resultMethod;
114     }
115 
116     /**
117      * Checks if stateful field is cleared during {@link AbstractCheck#beginTree} in check.
118      *
119      * @param check      check object which field is to be verified
120      * @param astToVisit ast to pass into check methods
121      * @param fieldName  name of the field to be checked
122      * @param isClear    function for checking field state
123      * @return {@code true} if state of the field is cleared
124      * @throws NoSuchFieldException   if there is no field with the
125      *                                {@code fieldName} in the {@code check}
126      * @throws IllegalAccessException if the field is inaccessible
127      */
128     public static boolean isStatefulFieldClearedDuringBeginTree(AbstractCheck check,
129                                                                 DetailAST astToVisit,
130                                                                 String fieldName,
131                                                                 Predicate<Object> isClear)
132             throws NoSuchFieldException, IllegalAccessException {
133         check.beginTree(astToVisit);
134         check.visitToken(astToVisit);
135         check.beginTree(null);
136         final Field resultField = getClassDeclaredField(check.getClass(), fieldName);
137         return isClear.test(resultField.get(check));
138     }
139 
140     /**
141      * Checks if stateful field is cleared during {@link AutomaticBean}'s finishLocalSetup.
142      *
143      * @param filter filter object which field is to be verified
144      * @param event event to pass into filter methods
145      * @param fieldName name of the field to be checked
146      * @param isClear function for checking field state
147      * @return {@code true} if state of the field is cleared
148      * @throws Exception if there was an error.
149      */
150     public static boolean isStatefulFieldClearedDuringLocalSetup(
151             TreeWalkerFilter filter, TreeWalkerAuditEvent event,
152             String fieldName, Predicate<Object> isClear) throws Exception {
153         filter.accept(event);
154         getClassDeclaredMethod(filter.getClass(), "finishLocalSetup").invoke(filter);
155         final Field resultField = getClassDeclaredField(filter.getClass(), fieldName);
156         return isClear.test(resultField.get(filter));
157     }
158 
159     /**
160      * Returns the default PackageObjectFactory with the default package names.
161      * @return the default PackageObjectFactory.
162      */
163     public static PackageObjectFactory getPackageObjectFactory() throws CheckstyleException {
164         final ClassLoader cl = TestUtil.class.getClassLoader();
165         final Set<String> packageNames = PackageNamesLoader.getPackageNames(cl);
166         return new PackageObjectFactory(packageNames, cl);
167     }
168 
169     /**
170      * Finds node of specified type among root children, siblings, siblings children
171      * on any deep level.
172      * @param root      DetailAST
173      * @param predicate predicate
174      * @return {@link Optional} of {@link DetailAST} node which matches the predicate.
175      */
176     public static Optional<DetailAST> findTokenInAstByPredicate(DetailAST root,
177                                                                 Predicate<DetailAST> predicate) {
178         DetailAST curNode = root;
179         while (!predicate.test(curNode)) {
180             DetailAST toVisit = curNode.getFirstChild();
181             while (curNode != null && toVisit == null) {
182                 toVisit = curNode.getNextSibling();
183                 if (toVisit == null) {
184                     curNode = curNode.getParent();
185                 }
186             }
187 
188             if (curNode == toVisit || curNode == root.getParent()) {
189                 curNode = null;
190                 break;
191             }
192 
193             curNode = toVisit;
194         }
195         return Optional.ofNullable(curNode);
196     }
197 
198 }