001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks;
020
021import java.util.Set;
022
023/**
024 * Utility class to resolve a class name to an actual class. Note that loaded
025 * classes are not initialized.
026 * <p>Limitations: this does not handle inner classes very well.</p>
027 *
028 * @author Oliver Burn
029 * @version 1.0
030 */
031public class ClassResolver
032{
033    /** name of the package to check if the class belongs to **/
034    private final String pkg;
035    /** set of imports to check against **/
036    private final Set<String> imports;
037    /** use to load classes **/
038    private final ClassLoader loader;
039
040    /**
041     * Creates a new <code>ClassResolver</code> instance.
042     *
043     * @param loader the ClassLoader to load classes with.
044     * @param pkg the name of the package the class may belong to
045     * @param imports set of imports to check if the class belongs to
046     */
047    public ClassResolver(ClassLoader loader, String pkg, Set<String> imports)
048    {
049        this.loader = loader;
050        this.pkg = pkg;
051        this.imports = imports;
052        imports.add("java.lang.*");
053    }
054
055    /**
056     * Attempts to resolve the Class for a specified name. The algorithm is
057     * to check:
058     * - fully qualified name
059     * - explicit imports
060     * - enclosing package
061     * - star imports
062     * @param name name of the class to resolve
063     * @param currentClass name of current class (for inner classes).
064     * @return the resolved class
065     * @throws ClassNotFoundException if unable to resolve the class
066     */
067    public Class<?> resolve(String name, String currentClass)
068        throws ClassNotFoundException
069    {
070        // See if the class is full qualified
071        Class<?> clazz = resolveQualifiedName(name);
072        if (clazz != null) {
073            return clazz;
074        }
075
076        // try matching explicit imports
077        for (String imp : imports) {
078            // Very important to add the "." in the check below. Otherwise you
079            // when checking for "DataException", it will match on
080            // "SecurityDataException". This has been the cause of a very
081            // difficult bug to resolve!
082            if (imp.endsWith("." + name)) {
083                clazz = resolveQualifiedName(imp);
084                if (clazz != null) {
085                    return clazz;
086                }
087
088            }
089        }
090
091        // See if in the package
092        if (!"".equals(pkg)) {
093            clazz = resolveQualifiedName(pkg + "." + name);
094            if (clazz != null) {
095                return clazz;
096            }
097        }
098
099        //inner class of this class???
100        if (!"".equals(currentClass)) {
101            final String innerClass = (!"".equals(pkg) ? (pkg + ".") : "")
102                + currentClass + "$" + name;
103            if (isLoadable(innerClass)) {
104                return safeLoad(innerClass);
105            }
106        }
107
108        // try star imports
109        for (String imp : imports) {
110            if (imp.endsWith(".*")) {
111                final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1)
112                    + name;
113                clazz = resolveQualifiedName(fqn);
114                if (clazz != null) {
115                    return clazz;
116                }
117            }
118        }
119
120        // Giving up, the type is unknown, so load the class to generate an
121        // exception
122        return safeLoad(name);
123    }
124
125    /**
126     * @return whether a specified class is loadable with safeLoad().
127     * @param name name of the class to check
128     */
129    public boolean isLoadable(String name)
130    {
131        try {
132            safeLoad(name);
133            return true;
134        }
135        catch (final ClassNotFoundException e) {
136            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 name 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 name)
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        return Class.forName(name, false, loader);
154    }
155
156    /**
157     * Tries to resolve a class for fully-specified name.
158     * @param name a given name of class.
159     * @return Class object for the given name or null.
160     */
161    private Class<?> resolveQualifiedName(final String name)
162    {
163        try {
164            if (isLoadable(name)) {
165                return safeLoad(name);
166            }
167            //Perhaps it's fully-qualified inner class
168            final int dot = name.lastIndexOf(".");
169            if (dot != -1) {
170                final String innerName =
171                    name.substring(0, dot) + "$" + name.substring(dot + 1);
172                if (isLoadable(innerName)) {
173                    return safeLoad(innerName);
174                }
175            }
176        }
177        catch (final ClassNotFoundException ex) {
178            // we shouldn't get this exception here,
179            // so this is unexpected runtime exception
180            throw new RuntimeException(ex);
181        }
182
183        return null;
184    }
185}