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.checks.javadoc;
21  
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  /**
26   * Utility class to resolve a class name to an actual class. Note that loaded
27   * classes are not initialized.
28   * <p>Limitations: this does not handle inner classes very well.</p>
29   *
30   * @author Oliver Burn
31   */
32  public class ClassResolver {
33  
34      /** Period literal. */
35      private static final String PERIOD = ".";
36      /** Dollar sign literal. */
37      private static final String DOLLAR_SIGN = "$";
38  
39      /** Name of the package to check if the class belongs to. **/
40      private final String pkg;
41      /** Set of imports to check against. **/
42      private final Set<String> imports;
43      /** Use to load classes. **/
44      private final ClassLoader loader;
45  
46      /**
47       * Creates a new {@code ClassResolver} instance.
48       *
49       * @param loader the ClassLoader to load classes with.
50       * @param pkg the name of the package the class may belong to
51       * @param imports set of imports to check if the class belongs to
52       */
53      public ClassResolver(ClassLoader loader, String pkg, Set<String> imports) {
54          this.loader = loader;
55          this.pkg = pkg;
56          this.imports = new HashSet<>(imports);
57          this.imports.add("java.lang.*");
58      }
59  
60      /**
61       * Attempts to resolve the Class for a specified name. The algorithm is
62       * to check:
63       * - fully qualified name
64       * - explicit imports
65       * - enclosing package
66       * - star imports
67       * @param name name of the class to resolve
68       * @param currentClass name of current class (for inner classes).
69       * @return the resolved class
70       * @throws ClassNotFoundException if unable to resolve the class
71       */
72      // -@cs[ForbidWildcardAsReturnType] This method can return any type, so no way to avoid wildcard
73      public Class<?> resolve(String name, String currentClass)
74              throws ClassNotFoundException {
75          // See if the class is full qualified
76          Class<?> clazz = resolveQualifiedName(name);
77          if (clazz == null) {
78              // try matching explicit imports
79              clazz = resolveMatchingExplicitImport(name);
80  
81              if (clazz == null) {
82                  // See if in the package
83                  clazz = resolveInPackage(name);
84  
85                  if (clazz == null) {
86                      // see if inner class of this class
87                      clazz = resolveInnerClass(name, currentClass);
88  
89                      if (clazz == null) {
90                          clazz = resolveByStarImports(name);
91                          // -@cs[NestedIfDepth] it is better to have single return point from method
92                          if (clazz == null) {
93                              // Giving up, the type is unknown, so load the class to generate an
94                              // exception
95                              clazz = safeLoad(name);
96                          }
97                      }
98                  }
99              }
100         }
101         return clazz;
102     }
103 
104     /**
105      * Try to find class by search in package.
106      * @param name class name
107      * @return class object
108      */
109     private Class<?> resolveInPackage(String name) {
110         Class<?> clazz = null;
111         if (pkg != null && !pkg.isEmpty()) {
112             final Class<?> classFromQualifiedName = resolveQualifiedName(pkg + PERIOD + name);
113             if (classFromQualifiedName != null) {
114                 clazz = classFromQualifiedName;
115             }
116         }
117         return clazz;
118     }
119 
120     /**
121      * Try to find class by matching explicit Import.
122      * @param name class name
123      * @return class object
124      */
125     private Class<?> resolveMatchingExplicitImport(String name) {
126         Class<?> clazz = null;
127         for (String imp : imports) {
128             // Very important to add the "." in the check below. Otherwise you
129             // when checking for "DataException", it will match on
130             // "SecurityDataException". This has been the cause of a very
131             // difficult bug to resolve!
132             if (imp.endsWith(PERIOD + name)) {
133                 clazz = resolveQualifiedName(imp);
134                 if (clazz != null) {
135                     break;
136                 }
137 
138             }
139         }
140         return clazz;
141     }
142 
143     /**
144      * See if inner class of this class.
145      * @param name name of the search Class to search
146      * @param currentClass class where search in
147      * @return class if found , or null if not resolved
148      * @throws ClassNotFoundException  if an error occurs
149      */
150     private Class<?> resolveInnerClass(String name, String currentClass)
151             throws ClassNotFoundException {
152         Class<?> clazz = null;
153         if (!currentClass.isEmpty()) {
154             String innerClass = currentClass + DOLLAR_SIGN + name;
155 
156             if (!pkg.isEmpty()) {
157                 innerClass = pkg + PERIOD + innerClass;
158             }
159 
160             if (isLoadable(innerClass)) {
161                 clazz = safeLoad(innerClass);
162             }
163         }
164         return clazz;
165     }
166 
167     /**
168      * Try star imports.
169      * @param name name of the Class to search
170      * @return  class if found , or null if not resolved
171      */
172     private Class<?> resolveByStarImports(String name) {
173         Class<?> clazz = null;
174         for (String imp : imports) {
175             if (imp.endsWith(".*")) {
176                 final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1) + name;
177                 clazz = resolveQualifiedName(fqn);
178                 if (clazz != null) {
179                     break;
180                 }
181             }
182         }
183         return clazz;
184     }
185 
186     /**
187      * Checks if the given class name can be loaded.
188      * @param name name of the class to check
189      * @return whether a specified class is loadable with safeLoad().
190      */
191     public boolean isLoadable(String name) {
192         boolean result;
193         try {
194             safeLoad(name);
195             result = true;
196         }
197         catch (final ClassNotFoundException | NoClassDefFoundError ignored) {
198             result = false;
199         }
200         return result;
201     }
202 
203     /**
204      * Will load a specified class is such a way that it will NOT be
205      * initialised.
206      * @param name name of the class to load
207      * @return the {@code Class} for the specified class
208      * @throws ClassNotFoundException if an error occurs
209      * @throws NoClassDefFoundError if an error occurs
210      */
211     // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
212     private Class<?> safeLoad(String name) throws ClassNotFoundException, NoClassDefFoundError {
213         // The next line will load the class using the specified class
214         // loader. The magic is having the "false" parameter. This means the
215         // class will not be initialised. Very, very important.
216         return Class.forName(name, false, loader);
217     }
218 
219     /**
220      * Tries to resolve a class for fully-specified name.
221      * @param name a given name of class.
222      * @return Class object for the given name or null.
223      */
224     private Class<?> resolveQualifiedName(final String name) {
225         Class<?> classObj = null;
226         try {
227             if (isLoadable(name)) {
228                 classObj = safeLoad(name);
229             }
230             else {
231                 //Perhaps it's fully-qualified inner class
232                 final int dot = name.lastIndexOf('.');
233                 if (dot != -1) {
234                     final String innerName =
235                         name.substring(0, dot) + DOLLAR_SIGN + name.substring(dot + 1);
236                     classObj = resolveQualifiedName(innerName);
237                 }
238             }
239         }
240         catch (final ClassNotFoundException ex) {
241             // we shouldn't get this exception here,
242             // so this is unexpected runtime exception
243             throw new IllegalStateException(ex);
244         }
245         return classObj;
246     }
247 }