##### Coverage Report - org.apache.any23.util.DiscoveryUtils

Classes in this File | Line Coverage | Branch Coverage | Complexity
DiscoveryUtils |
0% | 0/80
0% | 0/48
| 6.142857142857143;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 |  |
     }