Coverage Report - org.apache.any23.util.DiscoveryUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
DiscoveryUtils
0%
0/80
0%
0/48
6.143
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *  http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 
 18  
 package org.apache.any23.util;
 19  
 
 20  
 import java.io.File;
 21  
 import java.io.IOException;
 22  
 import java.io.UnsupportedEncodingException;
 23  
 import java.net.URL;
 24  
 import java.net.URLDecoder;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Collections;
 27  
 import java.util.Enumeration;
 28  
 import java.util.List;
 29  
 import java.util.jar.JarEntry;
 30  
 import java.util.jar.JarFile;
 31  
 
 32  
 /**
 33  
  * This class provides utility methods
 34  
  * for discovering classes in packages.
 35  
  *
 36  
  * @author Michele Mostarda (mostarda@fbk.eu)
 37  
  */
 38  0
 public class DiscoveryUtils {
 39  
 
 40  
     private static final String FILE_PREFIX  = "file:";
 41  
     private static final String CLASS_SUFFIX = ".class";
 42  
 
 43  
 
 44  
     /**
 45  
      * Scans all classes accessible from the context class loader
 46  
      * which belong to the given package and sub-packages.
 47  
      *
 48  
      * @param packageName the root package.
 49  
      * @return list of matching classes.
 50  
      * @throws IOException
 51  
      */
 52  
     public static List<Class> getClassesInPackage(String packageName) {
 53  0
         final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 54  0
         assert classLoader != null;
 55  0
         final String path = packageName.replace('.', '/');
 56  
         final Enumeration<URL> resources;
 57  
         try {
 58  0
             resources = classLoader.getResources(path);
 59  0
         } catch (IOException ioe) {
 60  0
             throw new IllegalStateException("Error while retrieving internal resource path.", ioe);
 61  0
         }
 62  0
         final List<File> dirs = new ArrayList<File>();
 63  0
         while (resources.hasMoreElements()) {
 64  0
             final URL resource = resources.nextElement();
 65  0
             final String fileName = resource.getFile();
 66  
             final String fileNameDecoded;
 67  
             try {
 68  0
                 fileNameDecoded = URLDecoder.decode(fileName, "UTF-8");
 69  0
             } catch (UnsupportedEncodingException uee) {
 70  0
                 throw new IllegalStateException("Error while decoding class file name.", uee);
 71  0
             }
 72  0
             dirs.add( new File(fileNameDecoded) );
 73  0
         }
 74  0
         final ArrayList<Class> classes = new ArrayList<Class>();
 75  0
         for (File directory : dirs) {
 76  0
             classes.addAll(findClasses(directory, packageName) );
 77  
         }
 78  0
         return classes;
 79  
     }
 80  
 
 81  
     /**
 82  
      * Scans all classes accessible from the context class loader
 83  
      * which belong to the given package and sub-packages and filter
 84  
      * them by ones implementing the specified interface <code>iface</code>.
 85  
      *
 86  
      * @param packageName the root package.
 87  
      * @param filter the interface/class filter.
 88  
      * @return list of matching classes.
 89  
      */
 90  
     public static List<Class> getClassesInPackage(String packageName, Class filter) {
 91  0
         final List<Class> classesInPackage = getClassesInPackage(packageName);
 92  0
         final List<Class> result = new ArrayList<Class>();
 93  
         Class superClazz;
 94  0
         for(Class clazz : classesInPackage) {
 95  0
             if(clazz.equals(filter)) {
 96  0
                 continue;
 97  
             }
 98  0
             superClazz = clazz.getSuperclass();
 99  0
             if( ( superClazz != null && superClazz.equals(filter) ) || contains(clazz.getInterfaces(), filter) ) {
 100  0
                 result.add(clazz);
 101  
             }
 102  
         }
 103  0
         return result;
 104  
     }
 105  
 
 106  
     /**
 107  
      * Find all classes within the specified location by package name.
 108  
      *
 109  
      * @param location class location.
 110  
      * @param packageName package name.
 111  
      * @return list of detected classes.
 112  
      */
 113  
     private static List<Class> findClasses(File location, String packageName) {
 114  0
         final String locationPath = location.getPath();
 115  0
         if( locationPath.indexOf(FILE_PREFIX) == 0 ) {
 116  0
             return findClassesInJAR(locationPath);
 117  
         }
 118  0
         return findClassesInDir(location, packageName);
 119  
     }
 120  
 
 121  
     /**
 122  
      * Find all classes within a JAR in a given prefix addressed with syntax
 123  
      * <code>file:<path/to.jar>!<path/to/package>.
 124  
      *
 125  
      * @param location package location.
 126  
      * @return list of detected classes.
 127  
      */
 128  
     private static List<Class> findClassesInJAR(String location) {
 129  0
         final String[] sections = location.split("!");
 130  0
         if(sections.length != 2) {
 131  0
             throw new IllegalArgumentException("Invalid JAR location.");
 132  
         }
 133  0
         final String jarLocation = sections[0].substring(FILE_PREFIX.length());
 134  0
         final String packagePath = sections[1].substring(1);
 135  
 
 136  
         try {
 137  0
             final JarFile jarFile = new JarFile(jarLocation);
 138  0
             final Enumeration<JarEntry> entries = jarFile.entries();
 139  0
             final List<Class> result = new ArrayList<Class>();
 140  
             JarEntry current;
 141  
             String entryName;
 142  
             String clazzName;
 143  
             Class clazz;
 144  0
             while(entries.hasMoreElements()) {
 145  0
                 current = entries.nextElement();
 146  0
                 entryName = current.getName();
 147  0
                 if(
 148  
                         StringUtils.isPrefix(packagePath, entryName)
 149  
                                 &&
 150  
                         StringUtils.isSuffix(CLASS_SUFFIX, entryName)
 151  
                                 &&
 152  
                         ! entryName.contains("$")
 153  
                 ) {
 154  
                     try {
 155  0
                         clazzName = entryName.substring(
 156  
                                 0, entryName.length() - CLASS_SUFFIX.length()
 157  
                         ).replaceAll("/",".");
 158  0
                         clazz = Class.forName(clazzName);
 159  0
                     } catch (ClassNotFoundException cnfe) {
 160  0
                         throw new IllegalStateException("Error while loading detected class.", cnfe);
 161  0
                     }
 162  0
                     result.add(clazz);
 163  
                 }
 164  
             }
 165  0
             return result;
 166  0
         } catch (IOException ioe) {
 167  0
             throw new RuntimeException("Error while opening JAR file.", ioe);
 168  
         }
 169  
     }
 170  
 
 171  
     /**
 172  
      * Recursive method used to find all classes in a given directory and sub-dirs.
 173  
      *
 174  
      * @param directory   The base directory
 175  
      * @param packageName The package name for classes found inside the base directory
 176  
      * @return The classes
 177  
      */
 178  
     private static List<Class> findClassesInDir(File directory, String packageName) {
 179  0
         if (!directory.exists()) {
 180  0
             return Collections.emptyList();
 181  
         }
 182  0
         final List<Class> classes = new ArrayList<Class>();
 183  0
         File[] files = directory.listFiles();
 184  0
         for (File file : files) {
 185  0
             String fileName = file.getName();
 186  0
             if (file.isDirectory()) {
 187  0
                 assert !fileName.contains(".");
 188  0
                 classes.addAll(findClassesInDir(file, packageName + "." + fileName));
 189  0
             } else if (fileName.endsWith(".class") && !fileName.contains("$")) {
 190  
                 try {
 191  
                     Class clazz;
 192  
                     try {
 193  0
                         clazz = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6));
 194  0
                     } catch (ExceptionInInitializerError e) {
 195  
                         /*
 196  
                         happen, for example, in classes, which depend on Spring to inject some beans,
 197  
                         and which fail, if dependency is not fulfilled
 198  
                         */
 199  0
                         clazz = Class.forName(
 200  
                                 packageName + '.' + fileName.substring(0, fileName.length() - 6),
 201  
                                 false,
 202  
                                 Thread.currentThread().getContextClassLoader()
 203  
                         );
 204  0
                     }
 205  0
                     classes.add(clazz);
 206  0
                 } catch (ClassNotFoundException cnfe) {
 207  0
                     throw new IllegalStateException("Error while loading detected class.", cnfe);
 208  0
                 }
 209  
             }
 210  
         }
 211  0
         return classes;
 212  
     }
 213  
 
 214  
     private static boolean contains(Object[] list, Object t) {
 215  0
         for(Object o : list) {
 216  0
             if( o.equals(t) ) {
 217  0
                 return true;
 218  
             }
 219  
         }
 220  0
         return false;
 221  
     }
 222  
 
 223  0
     private DiscoveryUtils(){}
 224  
 
 225  
 }