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