com.norconex.commons.lang.ClassFinder.java Source code

Java tutorial

Introduction

Here is the source code for com.norconex.commons.lang.ClassFinder.java

Source

/* Copyright 2010-2014 Norconex Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.norconex.commons.lang;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

/**
 * Utility class for finding names of classes implementing an interface or class
 * in directories or JAR files. In order to find if a class is potential
 * candidate, it is "loaded" first, but into a temporary class loader.  
 * Still, if it is important to you that classes do not get loaded, you can 
 * use other approaches, such as byte-code scanning.   See 
 * <a href="http://commons.apache.org/sandbox/classscan/">Apache ClassScan</a>
 * sandbox project for code that does that. 
 * 
 * @author Pascal Essiembre
 */
@SuppressWarnings("nls")
public final class ClassFinder {

    private static final Logger LOG = Logger.getLogger(ClassFinder.class);

    private ClassFinder() {
        super();
    }

    /**
     * Finds the names of all subtypes of the super class,
     * scanning the roots of this class classpath.
     * This method is null-safe.  If no classes are found, 
     * an empty list will be returned.
     * @param superClass the class from which to find subtypes
     * @return list of class names
     * @since 1.4.0
     */
    public static List<String> findSubTypes(Class<?> superClass) {
        List<String> classes = new ArrayList<String>();
        if (superClass == null) {
            return classes;
        }
        Enumeration<URL> roots;
        try {
            roots = ClassFinder.class.getClassLoader().getResources("");
        } catch (IOException e) {
            LOG.error("Cannot obtain class roots for class: " + superClass, e);
            return classes;
        }
        while (roots.hasMoreElements()) {
            URL url = roots.nextElement();
            File root = new File(url.getPath());
            classes.addAll(findSubTypes(root, superClass));
        }
        return classes;
    }

    /**
     * Finds the names of all subtypes of the super class in list
     * of {@link File} supplied.
     * This method is null-safe.  If no classes are found, 
     * an empty list will be returned.
     * @param files directories and/or JARs to scan for classes
     * @param superClass the class from which to find subtypes
     * @return list of class names
     * @since 1.4.0
     */
    public static List<String> findSubTypes(List<File> files, Class<?> superClass) {
        List<String> classes = new ArrayList<String>();
        if (superClass == null || files == null) {
            return classes;
        }
        for (File file : files) {
            classes.addAll(findSubTypes(file, superClass));
        }
        return classes;
    }

    /**
     * @deprecated since 1.4.0.  Replaced with 
     * {@link #findSubTypes(List, Class)}.
     * @param files directories and/or JARs to scan for classes
     * @param superClass the class from which to find subtypes
     * @return list of class names
     */
    @Deprecated
    public static List<String> findImplementors(List<File> files, Class<?> superClass) {
        return findSubTypes(files, superClass);
    }

    /**
     * Finds the names of all subtypes of the super class for the
     * supplied {@link File}.
     * This method is null-safe.  If no classes are found, 
     * an empty list will be returned.  
     * If the file is null or does not exists, or if it is not a JAR or 
     * directory, an empty string list will be returned.
     * @param file directory or JAR to scan for classes
     * @param superClass the class from which to find subtypes
     * @return list of class names
     * @since 1.4.0
     */
    public static List<String> findSubTypes(File file, Class<?> superClass) {
        if (superClass == null) {
            return new ArrayList<String>();
        }
        if (file == null || !file.exists()) {
            LOG.warn("Trying to find implementing classes from a null or " + "non-existant file: " + file);
            return new ArrayList<String>();
        }
        if (file.isDirectory()) {
            return findSubTypesFromDirectory(new File(file.getAbsolutePath() + "/"), superClass);
        }
        if (file.getName().endsWith(".jar")) {
            return findSubTypesFromJar(file, superClass);
        }
        LOG.warn("File not a JAR and not a directory.");
        return new ArrayList<String>();
    }

    /**
     * @deprecated since 1.4.0.  Replaced with 
     * {@link #findSubTypes(File, Class)}.
     * @param file directory or JAR to scan for classes
     * @param superClass the class from which to find subtypes
     * @return list of class names
     */
    public static List<String> findImplementors(File file, Class<?> superClass) {
        return findSubTypes(file, superClass);
    }

    private static List<String> findSubTypesFromDirectory(File dir, Class<?> superClass) {

        List<String> classes = new ArrayList<String>();
        String dirPath = dir.getAbsolutePath();

        Collection<File> classFiles = FileUtils.listFiles(dir, new String[] { "class" }, true);
        ClassLoader loader = getClassLoader(dir);
        if (loader == null) {
            return classes;
        }

        for (File classFile : classFiles) {
            String filePath = classFile.getAbsolutePath();
            String className = StringUtils.removeStart(filePath, dirPath);
            className = resolveName(loader, className, superClass);
            if (className != null) {
                classes.add(className);
            }
        }
        return classes;
    }

    private static List<String> findSubTypesFromJar(File jarFile, Class<?> superClass) {

        List<String> classes = new ArrayList<String>();
        ClassLoader loader = getClassLoader(jarFile);
        if (loader == null) {
            return classes;
        }
        JarFile jar = null;
        try {
            jar = new JarFile(jarFile);
        } catch (IOException e) {
            LOG.error("Invalid JAR: " + jarFile, e);
            return classes;
        }
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String className = entry.getName();
            className = resolveName(loader, className, superClass);
            if (className != null) {
                classes.add(className);
            }
        }
        try {
            jar.close();
        } catch (IOException e) {
            LOG.error("Could not close JAR.", e);
        }
        return classes;
    }

    private static ClassLoader getClassLoader(File url) {
        try {
            URL dirURL = url.toURI().toURL();
            return new URLClassLoader(new URL[] { dirURL }, ClassFinder.class.getClassLoader());
        } catch (MalformedURLException e) {
            LOG.error("Invalid classpath: " + url, e);
            return null;
        }
    }

    private static String resolveName(ClassLoader loader, String rawName, Class<?> superClass) {
        String className = rawName;
        if (!rawName.endsWith(".class") || className.contains("$")) {
            return null;
        }
        className = className.replaceAll("[\\\\/]", ".");
        className = StringUtils.removeStart(className, ".");
        className = StringUtils.removeEnd(className, ".class");

        try {
            Class<?> clazz = loader.loadClass(className);
            // load only concrete implementations
            if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())
                    && superClass.isAssignableFrom(clazz)) {
                return clazz.getName();
            }
        } catch (ClassNotFoundException e) {
            LOG.error("Invalid class: " + className, e);
        } catch (NoClassDefFoundError e) {
            LOG.debug("Invalid class: " + className, e);
        }
        return null;
    }

}