001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 * @author Oliver Burn 031 */ 032public class ClassResolver { 033 034 /** Period literal. */ 035 private static final String PERIOD = "."; 036 /** Dollar sign literal. */ 037 private static final String DOLLAR_SIGN = "$"; 038 039 /** Name of the package to check if the class belongs to. **/ 040 private final String pkg; 041 /** Set of imports to check against. **/ 042 private final Set<String> imports; 043 /** Use to load classes. **/ 044 private final ClassLoader loader; 045 046 /** 047 * Creates a new {@code ClassResolver} instance. 048 * 049 * @param loader the ClassLoader to load classes with. 050 * @param pkg the name of the package the class may belong to 051 * @param imports set of imports to check if the class belongs to 052 */ 053 public ClassResolver(ClassLoader loader, String pkg, Set<String> imports) { 054 this.loader = loader; 055 this.pkg = pkg; 056 this.imports = new HashSet<>(imports); 057 this.imports.add("java.lang.*"); 058 } 059 060 /** 061 * Attempts to resolve the Class for a specified name. The algorithm is 062 * to check: 063 * - fully qualified name 064 * - explicit imports 065 * - enclosing package 066 * - star imports 067 * @param name name of the class to resolve 068 * @param currentClass name of current class (for inner classes). 069 * @return the resolved class 070 * @throws ClassNotFoundException if unable to resolve the class 071 */ 072 // -@cs[ForbidWildcardAsReturnType] This method can return any type, so no way to avoid wildcard 073 public Class<?> resolve(String name, String currentClass) 074 throws ClassNotFoundException { 075 // See if the class is full qualified 076 Class<?> clazz = resolveQualifiedName(name); 077 if (clazz == null) { 078 // try matching explicit imports 079 clazz = resolveMatchingExplicitImport(name); 080 081 if (clazz == null) { 082 // See if in the package 083 clazz = resolveInPackage(name); 084 085 if (clazz == null) { 086 // see if inner class of this class 087 clazz = resolveInnerClass(name, currentClass); 088 089 if (clazz == null) { 090 clazz = resolveByStarImports(name); 091 // -@cs[NestedIfDepth] it is better to have single return point from method 092 if (clazz == null) { 093 // Giving up, the type is unknown, so load the class to generate an 094 // exception 095 clazz = safeLoad(name); 096 } 097 } 098 } 099 } 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 return clazz; 140 } 141 142 /** 143 * See if inner class of this class. 144 * @param name name of the search Class to search 145 * @param currentClass class where search in 146 * @return class if found , or null if not resolved 147 * @throws ClassNotFoundException if an error occurs 148 */ 149 private Class<?> resolveInnerClass(String name, String currentClass) 150 throws ClassNotFoundException { 151 Class<?> clazz = null; 152 if (!currentClass.isEmpty()) { 153 String innerClass = currentClass + DOLLAR_SIGN + name; 154 155 if (!pkg.isEmpty()) { 156 innerClass = pkg + PERIOD + innerClass; 157 } 158 159 if (isLoadable(innerClass)) { 160 clazz = safeLoad(innerClass); 161 } 162 } 163 return clazz; 164 } 165 166 /** 167 * Try star imports. 168 * @param name name of the Class to search 169 * @return class if found , or null if not resolved 170 */ 171 private Class<?> resolveByStarImports(String name) { 172 Class<?> clazz = null; 173 for (String imp : imports) { 174 if (imp.endsWith(".*")) { 175 final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1) + name; 176 clazz = resolveQualifiedName(fqn); 177 if (clazz != null) { 178 break; 179 } 180 } 181 } 182 return clazz; 183 } 184 185 /** 186 * Checks if the given class name can be loaded. 187 * @param name name of the class to check 188 * @return whether a specified class is loadable with safeLoad(). 189 */ 190 public boolean isLoadable(String name) { 191 boolean result; 192 try { 193 safeLoad(name); 194 result = true; 195 } 196 catch (final ClassNotFoundException | NoClassDefFoundError ignored) { 197 result = false; 198 } 199 return result; 200 } 201 202 /** 203 * Will load a specified class is such a way that it will NOT be 204 * initialised. 205 * @param name name of the class to load 206 * @return the {@code Class} for the specified class 207 * @throws ClassNotFoundException if an error occurs 208 * @throws NoClassDefFoundError if an error occurs 209 */ 210 // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon. 211 private Class<?> safeLoad(String name) throws ClassNotFoundException, NoClassDefFoundError { 212 // The next line will load the class using the specified class 213 // loader. The magic is having the "false" parameter. This means the 214 // class will not be initialised. Very, very important. 215 return Class.forName(name, false, loader); 216 } 217 218 /** 219 * Tries to resolve a class for fully-specified name. 220 * @param name a given name of class. 221 * @return Class object for the given name or null. 222 */ 223 private Class<?> resolveQualifiedName(final String name) { 224 Class<?> classObj = null; 225 try { 226 if (isLoadable(name)) { 227 classObj = safeLoad(name); 228 } 229 else { 230 //Perhaps it's fully-qualified inner class 231 final int dot = name.lastIndexOf('.'); 232 if (dot != -1) { 233 final String innerName = 234 name.substring(0, dot) + DOLLAR_SIGN + name.substring(dot + 1); 235 classObj = resolveQualifiedName(innerName); 236 } 237 } 238 } 239 catch (final ClassNotFoundException ex) { 240 // we shouldn't get this exception here, 241 // so this is unexpected runtime exception 242 throw new IllegalStateException(ex); 243 } 244 return classObj; 245 } 246 247}