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