| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| ClassResolver |
|
| 6.2;6.2 |
| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2014 Oliver Burn | |
| 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 | package com.puppycrawl.tools.checkstyle.checks; | |
| 20 | ||
| 21 | import java.util.Set; | |
| 22 | ||
| 23 | /** | |
| 24 | * Utility class to resolve a class name to an actual class. Note that loaded | |
| 25 | * classes are not initialized. | |
| 26 | * <p>Limitations: this does not handle inner classes very well.</p> | |
| 27 | * | |
| 28 | * @author Oliver Burn | |
| 29 | * @version 1.0 | |
| 30 | */ | |
| 31 | public class ClassResolver | |
| 32 | { | |
| 33 | /** name of the package to check if the class belongs to **/ | |
| 34 | private final String mPkg; | |
| 35 | /** set of imports to check against **/ | |
| 36 | private final Set<String> mImports; | |
| 37 | /** use to load classes **/ | |
| 38 | private final ClassLoader mLoader; | |
| 39 | ||
| 40 | /** | |
| 41 | * Creates a new <code>ClassResolver</code> instance. | |
| 42 | * | |
| 43 | * @param aLoader the ClassLoader to load classes with. | |
| 44 | * @param aPkg the name of the package the class may belong to | |
| 45 | * @param aImports set of imports to check if the class belongs to | |
| 46 | */ | |
| 47 | public ClassResolver(ClassLoader aLoader, String aPkg, Set<String> aImports) | |
| 48 | 19 | { |
| 49 | 19 | mLoader = aLoader; |
| 50 | 19 | mPkg = aPkg; |
| 51 | 19 | mImports = aImports; |
| 52 | 19 | mImports.add("java.lang.*"); |
| 53 | 19 | } |
| 54 | ||
| 55 | /** | |
| 56 | * Attempts to resolve the Class for a specified name. The algorithm is | |
| 57 | * to check: | |
| 58 | * - fully qualified name | |
| 59 | * - explicit imports | |
| 60 | * - enclosing package | |
| 61 | * - star imports | |
| 62 | * @param aName name of the class to resolve | |
| 63 | * @param aCurrentClass name of current class (for inner classes). | |
| 64 | * @return the resolved class | |
| 65 | * @throws ClassNotFoundException if unable to resolve the class | |
| 66 | */ | |
| 67 | public Class<?> resolve(String aName, String aCurrentClass) | |
| 68 | throws ClassNotFoundException | |
| 69 | { | |
| 70 | // See if the class is full qualified | |
| 71 | 129 | Class<?> clazz = resolveQualifiedName(aName); |
| 72 | 129 | if (clazz != null) { |
| 73 | 42 | return clazz; |
| 74 | } | |
| 75 | ||
| 76 | // try matching explicit imports | |
| 77 | 87 | for (String imp : mImports) { |
| 78 | // Very important to add the "." in the check below. Otherwise you | |
| 79 | // when checking for "DataException", it will match on | |
| 80 | // "SecurityDataException". This has been the cause of a very | |
| 81 | // difficult bug to resolve! | |
| 82 | 170 | if (imp.endsWith("." + aName)) { |
| 83 | 26 | clazz = resolveQualifiedName(imp); |
| 84 | 26 | if (clazz != null) { |
| 85 | 26 | return clazz; |
| 86 | } | |
| 87 | ||
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | // See if in the package | |
| 92 | 61 | if (!"".equals(mPkg)) { |
| 93 | 38 | clazz = resolveQualifiedName(mPkg + "." + aName); |
| 94 | 38 | if (clazz != null) { |
| 95 | 7 | return clazz; |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | //inner class of this class??? | |
| 100 | 54 | if (!"".equals(aCurrentClass)) { |
| 101 | 49 | final String innerClass = (!"".equals(mPkg) ? (mPkg + ".") : "") |
| 102 | + aCurrentClass + "$" + aName; | |
| 103 | 49 | if (isLoadable(innerClass)) { |
| 104 | 9 | return safeLoad(innerClass); |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | // try star imports | |
| 109 | 45 | for (String imp : mImports) { |
| 110 | 57 | if (imp.endsWith(".*")) { |
| 111 | 53 | final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1) |
| 112 | + aName; | |
| 113 | 53 | clazz = resolveQualifiedName(fqn); |
| 114 | 53 | if (clazz != null) { |
| 115 | 42 | return clazz; |
| 116 | } | |
| 117 | 15 | } |
| 118 | } | |
| 119 | ||
| 120 | // Giving up, the type is unknown, so load the class to generate an | |
| 121 | // exception | |
| 122 | 3 | return safeLoad(aName); |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * @return whether a specified class is loadable with safeLoad(). | |
| 127 | * @param aName name of the class to check | |
| 128 | */ | |
| 129 | public boolean isLoadable(String aName) | |
| 130 | { | |
| 131 | try { | |
| 132 | 348 | safeLoad(aName); |
| 133 | 126 | return true; |
| 134 | } | |
| 135 | 222 | catch (final ClassNotFoundException e) { |
| 136 | 222 | return false; |
| 137 | } | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Will load a specified class is such a way that it will NOT be | |
| 142 | * initialised. | |
| 143 | * @param aName name of the class to load | |
| 144 | * @return the <code>Class</code> for the specified class | |
| 145 | * @throws ClassNotFoundException if an error occurs | |
| 146 | */ | |
| 147 | public Class<?> safeLoad(String aName) | |
| 148 | throws ClassNotFoundException | |
| 149 | { | |
| 150 | // The next line will load the class using the specified class | |
| 151 | // loader. The magic is having the "false" parameter. This means the | |
| 152 | // class will not be initialised. Very, very important. | |
| 153 | 477 | return Class.forName(aName, false, mLoader); |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Tries to resolve a class for fully-specified name. | |
| 158 | * @param aName a given name of class. | |
| 159 | * @return Class object for the given name or null. | |
| 160 | */ | |
| 161 | private Class<?> resolveQualifiedName(final String aName) | |
| 162 | { | |
| 163 | try { | |
| 164 | 246 | if (isLoadable(aName)) { |
| 165 | 111 | return safeLoad(aName); |
| 166 | } | |
| 167 | //Perhaps it's fully-qualified inner class | |
| 168 | 135 | final int dot = aName.lastIndexOf("."); |
| 169 | 135 | if (dot != -1) { |
| 170 | 53 | final String innerName = |
| 171 | aName.substring(0, dot) + "$" + aName.substring(dot + 1); | |
| 172 | 53 | if (isLoadable(innerName)) { |
| 173 | 6 | return safeLoad(innerName); |
| 174 | } | |
| 175 | } | |
| 176 | } | |
| 177 | 0 | catch (final ClassNotFoundException ex) { |
| 178 | // we shouldn't get this exception here, | |
| 179 | // so this is unexpected runtime exception | |
| 180 | 0 | throw new RuntimeException(ex); |
| 181 | 129 | } |
| 182 | ||
| 183 | 129 | return null; |
| 184 | } | |
| 185 | } |