Coverage Report - org.mule.util.scan.ClasspathScanner
 
Classes in this File Line Coverage Branch Coverage Complexity
ClasspathScanner
0%
0/101
0%
0/60
0
 
 1  
 /*
 2  
  * $Id: ClasspathScanner.java 19977 2010-10-21 11:10:15Z rossmason $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 package org.mule.util.scan;
 11  
 
 12  
 import org.mule.config.ExceptionHelper;
 13  
 import org.mule.util.ClassUtils;
 14  
 import org.mule.util.FileUtils;
 15  
 import org.mule.util.scan.annotations.AnnotationFilter;
 16  
 import org.mule.util.scan.annotations.AnnotationTypeFilter;
 17  
 import org.mule.util.scan.annotations.AnnotationsScanner;
 18  
 import org.mule.util.scan.annotations.ClosableClassReader;
 19  
 import org.mule.util.scan.annotations.MetaAnnotationTypeFilter;
 20  
 
 21  
 import java.io.File;
 22  
 import java.io.FileInputStream;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.lang.annotation.Annotation;
 26  
 import java.lang.annotation.ElementType;
 27  
 import java.lang.annotation.Target;
 28  
 import java.lang.reflect.Modifier;
 29  
 import java.net.URL;
 30  
 import java.util.Collection;
 31  
 import java.util.Enumeration;
 32  
 import java.util.HashSet;
 33  
 import java.util.Set;
 34  
 import java.util.jar.JarEntry;
 35  
 import java.util.jar.JarFile;
 36  
 
 37  
 import org.apache.commons.logging.Log;
 38  
 import org.apache.commons.logging.LogFactory;
 39  
 import org.objectweb.asm.ClassReader;
 40  
 
 41  
 /**
 42  
  * This class can be used to scan the classpath for classtypes (or interfaces they implement) or for annotations on the classpath.
 43  
  * The type of scanner used depends on the class type passed in. There are currently 3 types of scanner;
 44  
  * <ul>
 45  
  * <li>{@link InterfaceClassScanner} - will search for all class that are assignable to the interface provided</li>
 46  
  * <li>{@link ImplementationClassScanner} - will search for all classes that extend a base type</li>
 47  
  * <li>{@link AnnotationsScanner} - will searhc for classes with specific annotations, this can also seach for meta annotations</li>
 48  
  * </ul>
 49  
  * This scanner uses ASM to search class byte code rather than the classes themselves making orders of magnitude better performance
 50  
  *  and uses a lot less memory. ASM seems to be the fasted of the byte code manipulation libraries i.e. JavaAssist or BCEL
 51  
  * Note that the scanner will not scan inner or anonymous classes.
 52  
  */
 53  
 public class ClasspathScanner
 54  
 {
 55  
     public static final int INCLUDE_ABSTRACT = 0x01;
 56  
     public static final int INCLUDE_INTERFACE = 0x02;
 57  
     public static final int INCLUDE_INNER = 0x04;
 58  
     public static final int INCLUDE_ANONYMOUS = 0x08;
 59  
 
 60  
     public static final int DEFAULT_FLAGS = 0x0;
 61  
     /**
 62  
      * logger used by this class
 63  
      */
 64  0
     protected transient final Log logger = LogFactory.getLog(ClasspathScanner.class);
 65  
 
 66  
     private ClassLoader classLoader;
 67  
     
 68  
     private String[] basepaths;
 69  
 
 70  
     public ClasspathScanner(String... basepaths)
 71  0
     {
 72  0
         this.classLoader = Thread.currentThread().getContextClassLoader();
 73  0
         this.basepaths = basepaths;
 74  0
     }
 75  
 
 76  
     public ClasspathScanner(ClassLoader classLoader, String... basepaths)
 77  0
     {
 78  0
         this.classLoader = classLoader;
 79  0
         this.basepaths = basepaths;
 80  0
     }
 81  
 
 82  
     public Set<Class> scanFor(Class clazz) throws IOException
 83  
     {
 84  0
         return scanFor(clazz, DEFAULT_FLAGS);
 85  
     }
 86  
 
 87  
     public Set<Class> scanFor(Class clazz, int flags) throws IOException
 88  
     {
 89  0
         Set<Class> classes = new HashSet<Class>();
 90  
 
 91  0
         for (int i = 0; i < basepaths.length; i++)
 92  
         {
 93  0
             String basepath = basepaths[i];
 94  
 
 95  0
             Enumeration<URL> urls = classLoader.getResources(basepath.trim());
 96  0
             while (urls.hasMoreElements())
 97  
             {
 98  0
                 URL url = urls.nextElement();
 99  0
                 if (url.getProtocol().equalsIgnoreCase("file"))
 100  
                 {
 101  0
                     classes.addAll(processFileUrl(url, basepath, clazz, flags));
 102  
                 }
 103  0
                 else if (url.getProtocol().equalsIgnoreCase("jar"))
 104  
                 {
 105  0
                     classes.addAll(processJarUrl(url, basepath, clazz, flags));
 106  
                 }
 107  
                 else
 108  
                 {
 109  0
                     throw new IllegalArgumentException("Do not understand how to handle protocol: " + url.getProtocol());
 110  
                 }
 111  0
             }
 112  
         }
 113  0
         return classes;
 114  
     }
 115  
 
 116  
     protected Set<Class> processJarUrl(URL url, String basepath, Class clazz, int flags) throws IOException
 117  
     {
 118  0
         Set<Class> set = new HashSet<Class>();
 119  0
         String path = url.getFile().substring(5, url.getFile().indexOf("!"));
 120  
         //We can't URLDecoder.decode(path) since some encoded chars are allowed on file uris
 121  0
         path = path.replaceAll("%20", " ");
 122  0
         JarFile jar = new JarFile(path);
 123  
 
 124  0
         for (Enumeration entries = jar.entries(); entries.hasMoreElements();)
 125  
         {
 126  0
             JarEntry entry = (JarEntry) entries.nextElement();
 127  0
             if (entry.getName().startsWith(basepath) && entry.getName().endsWith(".class"))
 128  
             {
 129  
                 try
 130  
                 {
 131  0
                     String name = entry.getName();
 132  
                     //Ignore anonymous and inner classes
 133  0
                     if (name.contains("$") && !hasFlag(flags, INCLUDE_INNER))
 134  
                     {
 135  0
                         continue;
 136  
                     }
 137  
                     
 138  0
                     URL classURL = classLoader.getResource(name);
 139  0
                     InputStream classStream = classURL.openStream();
 140  0
                     ClassReader reader = new ClosableClassReader(classStream);
 141  
 
 142  0
                     ClassScanner visitor = getScanner(clazz);
 143  0
                     reader.accept(visitor, 0);
 144  0
                     if (visitor.isMatch())
 145  
                     {
 146  0
                         addClassToSet(loadClass(visitor.getClassName()), set, flags);
 147  
                     }
 148  
                 }
 149  0
                 catch (Exception e)
 150  
                 {
 151  0
                     if (logger.isDebugEnabled())
 152  
                     {
 153  0
                         Throwable t = ExceptionHelper.getRootException(e);
 154  0
                         logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
 155  
                     }
 156  0
                 }
 157  
             }
 158  0
         }
 159  0
         jar.close();
 160  
         
 161  0
         return set;
 162  
     }
 163  
 
 164  
     protected boolean hasFlag(int flags, int flag)
 165  
     {
 166  0
         return (flags & flag) != 0;
 167  
     }
 168  
 
 169  
     protected Set<Class> processFileUrl(URL url, String basepath, Class clazz, int flags) throws IOException
 170  
     {
 171  0
         Set<Class> set = new HashSet<Class>();
 172  0
         String urlBase = url.getFile();
 173  
         //We can't URLDecoder.decode(path) since some encoded chars are allowed on file uris
 174  0
         urlBase = urlBase.replaceAll("%20", " ");
 175  0
         File dir = new File(urlBase);
 176  0
         if(!dir.isDirectory())
 177  
         {
 178  0
             logger.warn("Cannot process File URL: " + url + ". Path is not a directory");
 179  0
             return set;
 180  
         }
 181  
 
 182  0
         Collection<File> files = FileUtils.listFiles(new File(urlBase), new String[]{"class"}, true);
 183  0
         for (File file : files)
 184  
         {
 185  
             try
 186  
             {
 187  
                 //Ignore anonymous and inner classes
 188  0
                 if (file.getName().contains("$") && !hasFlag(flags, INCLUDE_INNER))
 189  
                 {
 190  0
                     continue;
 191  
                 }
 192  0
                 InputStream classStream = new FileInputStream(file);
 193  0
                 ClassReader reader = new ClosableClassReader(classStream);
 194  
 
 195  0
                 ClassScanner visitor = getScanner(clazz);
 196  0
                 reader.accept(visitor, 0);
 197  0
                 if (visitor.isMatch())
 198  
                 {
 199  0
                     addClassToSet(loadClass(visitor.getClassName()), set, flags);
 200  
                 }
 201  
             }
 202  0
             catch (IOException e)
 203  
             {
 204  0
                 if (logger.isDebugEnabled())
 205  
                 {
 206  0
                     Throwable t = ExceptionHelper.getRootException(e);
 207  0
                     logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
 208  
                 }
 209  0
             }
 210  
         }
 211  0
         return set;
 212  
     }
 213  
 
 214  
     protected void addClassToSet(Class c, Set<Class> set, int flags)
 215  
     {
 216  0
         if (c != null)
 217  
         {
 218  0
             synchronized (set)
 219  
             {
 220  0
                 if(c.isInterface())
 221  
                 {
 222  0
                     if(hasFlag(flags, INCLUDE_INTERFACE)) set.add(c);
 223  
                 }
 224  0
                 else if(Modifier.isAbstract(c.getModifiers()))
 225  
                 {
 226  0
                     if(hasFlag(flags, INCLUDE_ABSTRACT)) set.add(c);
 227  
                 }
 228  
                 else
 229  
                 {
 230  0
                     set.add(c);
 231  
                 }
 232  0
             }
 233  
         }
 234  0
     }
 235  
 
 236  
     protected Class loadClass(String name)
 237  
     {
 238  0
         String c = name.replace("/", ".");
 239  
         try
 240  
         {
 241  0
             return ClassUtils.loadClass(c, classLoader);
 242  
         }
 243  0
         catch (ClassNotFoundException e)
 244  
         {
 245  0
             if (logger.isWarnEnabled())
 246  
             {
 247  0
                 logger.warn(String.format("%s : %s", c, e.toString()));
 248  
             }
 249  0
             return null;
 250  
         }
 251  
     }
 252  
 
 253  
     /**
 254  
      * Works out the correct scanner based on the class passed in
 255  
      * <p/>
 256  
      * Note that these could be better architected by breaking out filters into strategy objects, but for now this
 257  
      * suits my needs
 258  
      *
 259  
      * @param clazz the type to scan for
 260  
      * @return a scanner suitable for handling the type passed in
 261  
      * @see AnnotationsScanner
 262  
      * @see InterfaceClassScanner
 263  
      * @see ImplementationClassScanner
 264  
      */
 265  
     protected ClassScanner getScanner(Class clazz)
 266  
     {
 267  0
         if (clazz.isInterface())
 268  
         {
 269  0
             if (clazz.isAnnotation())
 270  
             {
 271  0
                 AnnotationFilter filter = null;
 272  0
                 Annotation[] annos = clazz.getDeclaredAnnotations();
 273  0
                 for (int i = 0; i < annos.length; i++)
 274  
                 {
 275  0
                     Annotation anno = annos[i];
 276  0
                     if (anno instanceof Target)
 277  
                     {
 278  0
                         if (((Target) anno).value()[0] == ElementType.ANNOTATION_TYPE)
 279  
                         {
 280  0
                             filter = new MetaAnnotationTypeFilter(clazz, classLoader);
 281  
                         }
 282  
                     }
 283  
                 }
 284  0
                 if (filter == null)
 285  
                 {
 286  0
                     filter = new AnnotationTypeFilter(clazz);
 287  
                 }
 288  0
                 return new AnnotationsScanner(filter);
 289  
             }
 290  
             else
 291  
             {
 292  0
                 return new InterfaceClassScanner(clazz);
 293  
             }
 294  
         }
 295  
         else
 296  
         {
 297  0
             return new ImplementationClassScanner(clazz);
 298  
         }
 299  
     }
 300  
 }