001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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; 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 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 return clazz; 078 } 079 080 // try matching explicit imports 081 for (String imp : imports) { 082 // Very important to add the "." in the check below. Otherwise you 083 // when checking for "DataException", it will match on 084 // "SecurityDataException". This has been the cause of a very 085 // difficult bug to resolve! 086 if (imp.endsWith(PERIOD + name)) { 087 clazz = resolveQualifiedName(imp); 088 if (clazz != null) { 089 return clazz; 090 } 091 092 } 093 } 094 095 // See if in the package 096 if (pkg != null && !pkg.isEmpty()) { 097 final Class<?> classFromQualifiedName = resolveQualifiedName(pkg + PERIOD + name); 098 if (classFromQualifiedName != null) { 099 return classFromQualifiedName; 100 } 101 } 102 103 // see if inner class of this class 104 final Class<?> innerClass = resolveInnerClass(name, currentClass); 105 if (innerClass != null) { 106 return innerClass; 107 } 108 109 final Class<?> classFromStarImport = resolveByStarImports(name); 110 if (classFromStarImport != null) { 111 return classFromStarImport; 112 } 113 114 // Giving up, the type is unknown, so load the class to generate an 115 // exception 116 return safeLoad(name); 117 } 118 119 /** 120 * See if inner class of this class. 121 * @param name name of the search Class to search 122 * @param currentClass class where search in 123 * @return class if found , or null if not resolved 124 * @throws ClassNotFoundException if an error occurs 125 */ 126 private Class<?> resolveInnerClass(String name, String currentClass) 127 throws ClassNotFoundException { 128 Class<?> clazz = null; 129 if (!currentClass.isEmpty()) { 130 String innerClass = currentClass + DOLLAR_SIGN + name; 131 132 if (!pkg.isEmpty()) { 133 innerClass = pkg + PERIOD + innerClass; 134 } 135 136 if (isLoadable(innerClass)) { 137 clazz = safeLoad(innerClass); 138 } 139 } 140 return clazz; 141 } 142 143 /** 144 * Try star imports. 145 * @param name name of the Class to search 146 * @return class if found , or null if not resolved 147 */ 148 private Class<?> resolveByStarImports(String name) { 149 Class<?> clazz = null; 150 for (String imp : imports) { 151 if (imp.endsWith(".*")) { 152 final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1) 153 + name; 154 clazz = resolveQualifiedName(fqn); 155 if (clazz != null) { 156 break; 157 } 158 } 159 } 160 return clazz; 161 } 162 163 /** 164 * @param name name of the class to check 165 * @return whether a specified class is loadable with safeLoad(). 166 */ 167 public boolean isLoadable(String name) { 168 try { 169 safeLoad(name); 170 return true; 171 } 172 catch (final ClassNotFoundException ignored) { 173 return false; 174 } 175 } 176 177 /** 178 * Will load a specified class is such a way that it will NOT be 179 * initialised. 180 * @param name name of the class to load 181 * @return the {@code Class} for the specified class 182 * @throws ClassNotFoundException if an error occurs 183 */ 184 public Class<?> safeLoad(String name) throws ClassNotFoundException { 185 // The next line will load the class using the specified class 186 // loader. The magic is having the "false" parameter. This means the 187 // class will not be initialised. Very, very important. 188 return Class.forName(name, false, loader); 189 } 190 191 /** 192 * Tries to resolve a class for fully-specified name. 193 * @param name a given name of class. 194 * @return Class object for the given name or null. 195 */ 196 private Class<?> resolveQualifiedName(final String name) { 197 Class<?> classObj = null; 198 try { 199 if (isLoadable(name)) { 200 classObj = safeLoad(name); 201 } 202 else { 203 //Perhaps it's fully-qualified inner class 204 final int dot = name.lastIndexOf('.'); 205 if (dot != -1) { 206 final String innerName = 207 name.substring(0, dot) + DOLLAR_SIGN + name.substring(dot + 1); 208 classObj = resolveQualifiedName(innerName); 209 } 210 } 211 } 212 catch (final ClassNotFoundException ex) { 213 // we shouldn't get this exception here, 214 // so this is unexpected runtime exception 215 throw new IllegalStateException(ex); 216 } 217 return classObj; 218 } 219}