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