001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.HashSet; 023import java.util.Set; 024 025/** 026 * Utility class to resolve a class name to an actual class. Note that loaded 027 * classes are not initialized. 028 * <p>Limitations: this does not handle inner classes very well.</p> 029 * 030 */ 031public class ClassResolver { 032 033 /** Period literal. */ 034 private static final String PERIOD = "."; 035 /** Dollar sign literal. */ 036 private static final String DOLLAR_SIGN = "$"; 037 038 /** Name of the package to check if the class belongs to. **/ 039 private final String pkg; 040 /** Set of imports to check against. **/ 041 private final Set<String> imports; 042 /** Use to load classes. **/ 043 private final ClassLoader loader; 044 045 /** 046 * Creates a new {@code ClassResolver} instance. 047 * 048 * @param loader the ClassLoader to load classes with. 049 * @param pkg the name of the package the class may belong to 050 * @param imports set of imports to check if the class belongs to 051 */ 052 public ClassResolver(ClassLoader loader, String pkg, Set<String> imports) { 053 this.loader = loader; 054 this.pkg = pkg; 055 this.imports = new HashSet<>(imports); 056 this.imports.add("java.lang.*"); 057 } 058 059 /** 060 * Attempts to resolve the Class for a specified name. The algorithm is 061 * to check: 062 * - fully qualified name 063 * - explicit imports 064 * - enclosing package 065 * - star imports 066 * @param name name of the class to resolve 067 * @param currentClass name of current class (for inner classes). 068 * @return the resolved class 069 * @throws ClassNotFoundException if unable to resolve the class 070 */ 071 // -@cs[ForbidWildcardAsReturnType] This method can return any type, so no way to avoid wildcard 072 public Class<?> resolve(String name, String currentClass) 073 throws ClassNotFoundException { 074 // See if the class is full qualified 075 Class<?> clazz = resolveQualifiedName(name); 076 if (clazz == null) { 077 // try matching explicit imports 078 clazz = resolveMatchingExplicitImport(name); 079 080 if (clazz == null) { 081 // See if in the package 082 clazz = resolveInPackage(name); 083 084 if (clazz == null) { 085 // see if inner class of this class 086 clazz = resolveInnerClass(name, currentClass); 087 088 if (clazz == null) { 089 clazz = resolveByStarImports(name); 090 // -@cs[NestedIfDepth] it is better to have single return point from method 091 if (clazz == null) { 092 throw new ClassNotFoundException(name); 093 } 094 } 095 } 096 } 097 } 098 return clazz; 099 } 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}