View Javadoc

    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 for discovering classes in packages.
    34   *
    35   * @author Michele Mostarda (mostarda@fbk.eu)
    36   */
    37  public class DiscoveryUtils {
    38
    39      private static final String FILE_PREFIX = "file:";
    40      private static final String CLASS_SUFFIX = ".class";
    41
    42      /**
    43       * Scans all classes accessible from the context class loader which belong to the given package and sub-packages.
    44       *
    45       * @param packageName
    46       *            the root package.
    47       *
    48       * @return list of matching classes.
    49       */
    50      public static List<Class> getClassesInPackage(String packageName) {
    51          final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    52          assert classLoader != null;
    53          final String path = packageName.replace('.', '/');
    54          final Enumeration<URL> resources;
    55          try {
    56              resources = classLoader.getResources(path);
    57          } catch (IOException ioe) {
    58              throw new IllegalStateException("Error while retrieving internal resource path.", ioe);
    59          }
    60          final List<File> dirs = new ArrayList<File>();
    61          while (resources.hasMoreElements()) {
    62              final URL resource = resources.nextElement();
    63              final String fileName = resource.getFile();
    64              final String fileNameDecoded;
    65              try {
    66                  fileNameDecoded = URLDecoder.decode(fileName, "UTF-8");
    67              } catch (UnsupportedEncodingException uee) {
    68                  throw new IllegalStateException("Error while decoding class file name.", uee);
    69              }
    70              dirs.add(new File(fileNameDecoded));
    71          }
    72          @SuppressWarnings("rawtypes")
    73          final ArrayList<Class> classes = new ArrayList<Class>();
    74          for (File directory : dirs) {
    75              classes.addAll(findClasses(directory, packageName));
    76          }
    77          return classes;
    78      }
    79
    80      /**
    81       * Scans all classes accessible from the context class loader which belong to the given package and sub-packages and
    82       * filter them by ones implementing the specified interface <code>iface</code>.
    83       *
    84       * @param packageName
    85       *            the root package.
    86       * @param filter
    87       *            the interface/class filter.
    88       *
    89       * @return list of matching classes.
    90       */
    91      public static List<Class> getClassesInPackage(String packageName, Class<?> filter) {
    92          final List<Class> classesInPackage = getClassesInPackage(packageName);
    93          @SuppressWarnings("rawtypes")
    94          final List<Class> result = new ArrayList<Class>();
    95          Class<?> superClazz;
    96          for (Class<?> clazz : classesInPackage) {
    97              if (clazz.equals(filter)) {
    98                  continue;
    99              }
    100             superClazz = clazz.getSuperclass();
    101             if ((superClazz != null && superClazz.equals(filter)) || contains(clazz.getInterfaces(), filter)) {
    102                 result.add(clazz);
    103             }
    104         }
    105         return result;
    106     }
    107
    108     /**
    109      * Find all classes within the specified location by package name.
    110      *
    111      * @param location
    112      *            class location.
    113      * @param packageName
    114      *            package name.
    115      *
    116      * @return list of detected classes.
    117      */
    118     private static List<Class> findClasses(File location, String packageName) {
    119         final String locationPath = location.getPath();
    120         if (locationPath.indexOf(FILE_PREFIX) == 0) {
    121             return findClassesInJAR(locationPath);
    122         }
    123         return findClassesInDir(location, packageName);
    124     }
    125
    126     /**
    127      * Find all classes within a JAR in a given prefix addressed with syntax <code>file:<path/to.jar>!<path/to/package>.
    128      *
    129      * @param location
    130      *            package location.
    131      *
    132      * @return list of detected classes.
    133      */
    134     private static List<Class> findClassesInJAR(String location) {
    135         final String[] sections = location.split("!");
    136         if (sections.length != 2) {
    137             throw new IllegalArgumentException("Invalid JAR location.");
    138         }
    139         final String jarLocation = sections[0].substring(FILE_PREFIX.length());
    140         final String packagePath = sections[1].substring(1);
    141
    142         try {
    143             @SuppressWarnings("resource")
    144             final JarFile jarFile = new JarFile(jarLocation);
    145             final Enumeration<JarEntry> entries = jarFile.entries();
    146             @SuppressWarnings("rawtypes")
    147             final List<Class> result = new ArrayList<Class>();
    148             JarEntry current;
    149             String entryName;
    150             String clazzName;
    151             Class<?> clazz;
    152             while (entries.hasMoreElements()) {
    153                 current = entries.nextElement();
    154                 entryName = current.getName();
    155                 if (StringUtils.isPrefix(packagePath, entryName) && StringUtils.isSuffix(CLASS_SUFFIX, entryName)
    156                         && !entryName.contains("$")) {
    157                     try {
    158                         clazzName = entryName.substring(0, entryName.length() - CLASS_SUFFIX.length()).replaceAll("/",
    159                                 ".");
    160                         clazz = Class.forName(clazzName);
    161                     } catch (ClassNotFoundException cnfe) {
    162                         throw new IllegalStateException("Error while loading detected class.", cnfe);
    163                     }
    164                     result.add(clazz);
    165                 }
    166             }
    167             return result;
    168         } catch (IOException ioe) {
    169             throw new RuntimeException("Error while opening JAR file.", ioe);
    170         }
    171     }
    172
    173     /**
    174      * Recursive method used to find all classes in a given directory and sub-dirs.
    175      *
    176      * @param directory
    177      *            The base directory
    178      * @param packageName
    179      *            The package name for classes found inside the base directory
    180      *
    181      * @return The classes
    182      */
    183     private static List<Class> findClassesInDir(File directory, String packageName) {
    184         if (!directory.exists()) {
    185             return Collections.emptyList();
    186         }
    187         @SuppressWarnings("rawtypes")
    188         final List<Class> classes = new ArrayList<Class>();
    189         File[] files = directory.listFiles();
    190         for (File file : files) {
    191             String fileName = file.getName();
    192             if (file.isDirectory()) {
    193                 assert !fileName.contains(".");
    194                 classes.addAll(findClassesInDir(file, packageName + "." + fileName));
    195             } else if (fileName.endsWith(".class") && !fileName.contains("$")) {
    196                 try {
    197                     Class<?> clazz;
    198                     try {
    199                         clazz = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6));
    200                     } catch (ExceptionInInitializerError e) {
    201                         /*
    202                          * happen, for example, in classes, which depend on Spring to inject some beans, and which fail,
    203                          * if dependency is not fulfilled
    204                          */
    205                         clazz = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6), false,
    206                                 Thread.currentThread().getContextClassLoader());
    207                     }
    208                     classes.add(clazz);
    209                 } catch (ClassNotFoundException cnfe) {
    210                     throw new IllegalStateException("Error while loading detected class.", cnfe);
    211                 }
    212             }
    213         }
    214         return classes;
    215     }
    216
    217     private static boolean contains(Object[] list, Object t) {
    218         for (Object o : list) {
    219             if (o.equals(t)) {
    220                 return true;
    221             }
    222         }
    223         return false;
    224     }
    225
    226     private DiscoveryUtils() {
    227     }
    228
    229 }