Package org.mule.util.scan

Source Code of org.mule.util.scan.ClasspathScanner

/*
* $Id: ClasspathScanner.java 22231 2011-06-21 09:55:51Z dirk.olmes $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.util.scan;

import org.mule.config.ExceptionHelper;
import org.mule.util.ClassUtils;
import org.mule.util.FileUtils;
import org.mule.util.scan.annotations.AnnotationFilter;
import org.mule.util.scan.annotations.AnnotationTypeFilter;
import org.mule.util.scan.annotations.AnnotationsScanner;
import org.mule.util.scan.annotations.ClosableClassReader;
import org.mule.util.scan.annotations.MetaAnnotationTypeFilter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.ClassReader;

/**
* This class can be used to scan the classpath for classtypes (or interfaces they
* implement) or for annotations on the classpath. The type of scanner used depends
* on the class type passed in. There are currently 3 types of scanner;
* <ul>
* <li>{@link InterfaceClassScanner} - will search for all class that are assignable
* to the interface provided</li>
* <li>{@link ImplementationClassScanner} - will search for all classes that extend a
* base type</li>
* <li>{@link AnnotationsScanner} - will search for classes with specific
* annotations, this can also seach for meta annotations</li>
* </ul>
* This scanner uses ASM to search class byte code rather than the classes themselves
* making orders of magnitude better performance and uses a lot less memory. ASM
* seems to be the fasted of the byte code manipulation libraries i.e. JavaAssist or
* BCEL Note that the scanner will not scan inner or anonymous classes.
*/
public class ClasspathScanner
{
    public static final int INCLUDE_ABSTRACT = 0x01;
    public static final int INCLUDE_INTERFACE = 0x02;
    public static final int INCLUDE_INNER = 0x04;
    public static final int INCLUDE_ANONYMOUS = 0x08;

    public static final int DEFAULT_FLAGS = 0x0;
   
    /**
     * logger used by this class
     */
    protected transient final Log logger = LogFactory.getLog(ClasspathScanner.class);

    private ClassLoader classLoader;
   
    private String[] basepaths;

    public ClasspathScanner(String... basepaths)
    {
        this.classLoader = Thread.currentThread().getContextClassLoader();
        this.basepaths = basepaths;
    }

    public ClasspathScanner(ClassLoader classLoader, String... basepaths)
    {
        this.classLoader = classLoader;
        this.basepaths = basepaths;
    }

    public <T> Set<Class<T>> scanFor(Class<T> clazz) throws IOException
    {
        return scanFor(clazz, DEFAULT_FLAGS);
    }

    public <T> Set<Class<T>> scanFor(Class<T> clazz, int flags) throws IOException
    {
        Set<Class<T>> classes = new HashSet<Class<T>>();

        for (int i = 0; i < basepaths.length; i++)
        {
            String basepath = basepaths[i];

            Enumeration<URL> urls = classLoader.getResources(basepath.trim());
            while (urls.hasMoreElements())
            {
                URL url = urls.nextElement();
                if (url.getProtocol().equalsIgnoreCase("file"))
                {
                    classes.addAll(processFileUrl(url, basepath, clazz, flags));
                }
                else if (url.getProtocol().equalsIgnoreCase("jar"))
                {
                    classes.addAll(processJarUrl(url, basepath, clazz, flags));
                }
                else
                {
                    throw new IllegalArgumentException("Do not understand how to handle protocol: " + url.getProtocol());
                }
            }
        }
        return classes;
    }

    protected <T> Set<Class<T>> processJarUrl(URL url, String basepath, Class<T> clazz, int flags) throws IOException
    {
        Set<Class<T>> set = new HashSet<Class<T>>();
        String path = url.getFile().substring(5, url.getFile().indexOf("!"));
        // We can't URLDecoder.decode(path) since some encoded chars are allowed on file uris
        path = path.replaceAll("%20", " ");
        JarFile jar = new JarFile(path);

        for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();)
        {
            JarEntry entry = entries.nextElement();
            if (entry.getName().startsWith(basepath) && entry.getName().endsWith(".class"))
            {
                try
                {
                    String name = entry.getName();
                    // Ignore anonymous and inner classes
                    if (name.contains("$") && !hasFlag(flags, INCLUDE_INNER))
                    {
                        continue;
                    }
                   
                    URL classURL = classLoader.getResource(name);
                    InputStream classStream = classURL.openStream();
                    ClassReader reader = new ClosableClassReader(classStream);

                    ClassScanner visitor = getScanner(clazz);
                    reader.accept(visitor, 0);
                    if (visitor.isMatch())
                    {
                        @SuppressWarnings("unchecked")
                        Class<T> loadedClass = (Class<T>) loadClass(visitor.getClassName());
                        addClassToSet(loadedClass, set, flags);
                    }
                }
                catch (Exception e)
                {
                    if (logger.isDebugEnabled())
                    {
                        Throwable t = ExceptionHelper.getRootException(e);
                        logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
                    }
                }
            }
        }
        jar.close();
       
        return set;
    }

    protected boolean hasFlag(int flags, int flag)
    {
        return (flags & flag) != 0;
    }

    protected <T> Set<Class<T>> processFileUrl(URL url, String basepath, Class<T> clazz, int flags) throws IOException
    {
        Set<Class<T>> set = new HashSet<Class<T>>();
        String urlBase = url.getFile();
        //We can't URLDecoder.decode(path) since some encoded chars are allowed on file uris
        urlBase = urlBase.replaceAll("%20", " ");
        File dir = new File(urlBase);
        if(!dir.isDirectory())
        {
            logger.warn("Cannot process File URL: " + url + ". Path is not a directory");
            return set;
        }

        @SuppressWarnings("unchecked")
        Collection<File> files = FileUtils.listFiles(new File(urlBase), new String[]{"class"}, true);
        for (File file : files)
        {
            try
            {
                //Ignore anonymous and inner classes
                if (file.getName().contains("$") && !hasFlag(flags, INCLUDE_INNER))
                {
                    continue;
                }
                InputStream classStream = new FileInputStream(file);
                ClassReader reader = new ClosableClassReader(classStream);

                ClassScanner visitor = getScanner(clazz);
                reader.accept(visitor, 0);
                if (visitor.isMatch())
                {
                    @SuppressWarnings("unchecked")
                    Class<T> loadedClass = (Class<T>) loadClass(visitor.getClassName());
                    addClassToSet(loadedClass, set, flags);
                }
            }
            catch (IOException e)
            {
                if (logger.isDebugEnabled())
                {
                    Throwable t = ExceptionHelper.getRootException(e);
                    logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
                }
            }
        }
        return set;
    }

    protected <T> void addClassToSet(Class<T> c, Set<Class<T>> set, int flags)
    {
        if (c != null)
        {
            synchronized (set)
            {
                if (c.isInterface())
                {
                    if (hasFlag(flags, INCLUDE_INTERFACE))
                    {
                        set.add(c);
                    }
                }
                else if (Modifier.isAbstract(c.getModifiers()))
                {
                    if (hasFlag(flags, INCLUDE_ABSTRACT))
                    {
                        set.add(c);
                    }
                }
                else
                {
                    set.add(c);
                }
            }
        }
    }

    protected Class<?> loadClass(String name)
    {
        String c = name.replace("/", ".");
        try
        {
            return ClassUtils.loadClass(c, classLoader);
        }
        catch (ClassNotFoundException e)
        {
            if (logger.isWarnEnabled())
            {
                logger.warn(String.format("%s : %s", c, e.toString()));
            }
            return null;
        }
    }

    /**
     * Works out the correct scanner based on the class passed in
     * <p/>
     * Note that these could be better architected by breaking out filters into strategy objects, but for now this
     * suits my needs
     *
     * @param clazz the type to scan for
     * @return a scanner suitable for handling the type passed in
     * @see AnnotationsScanner
     * @see InterfaceClassScanner
     * @see ImplementationClassScanner
     */
    protected ClassScanner getScanner(Class<?> clazz)
    {
        if (clazz.isInterface())
        {
            if (clazz.isAnnotation())
            {
                @SuppressWarnings("unchecked")
                Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz;
               
                AnnotationFilter filter = null;
                Annotation[] annos = clazz.getDeclaredAnnotations();
                for (int i = 0; i < annos.length; i++)
                {
                    Annotation anno = annos[i];
                    if (anno instanceof Target)
                    {
                        if (((Target) anno).value()[0] == ElementType.ANNOTATION_TYPE)
                        {
                            filter = new MetaAnnotationTypeFilter(annotationClass, classLoader);
                        }
                    }
                }
                if (filter == null)
                {
                    filter = new AnnotationTypeFilter(annotationClass);
                }
                return new AnnotationsScanner(filter);
            }
            else
            {
                return new InterfaceClassScanner(clazz);
            }
        }
        else
        {
            return new ImplementationClassScanner(clazz);
        }
    }
}
TOP

Related Classes of org.mule.util.scan.ClasspathScanner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.