##### Coverage Report - org.apache.any23.plugin.Any23PluginManager

Classes in this File | Line Coverage | Branch Coverage | Complexity
Any23PluginManager |
0% | 0/159
0% | 0/98
| 4.0;4
Any23PluginManager$1 |
0% | 0/2
N/A | N/A
| 4.0;4
Any23PluginManager$2 |
0% | 0/2
0% | 0/4
| 4.0;4
Any23PluginManager$3 |
0% | 0/2
0% | 0/4
| 4.0;4
Any23PluginManager$4 |
0% | 0/6
0% | 0/6
| 4.0;4
Any23PluginManager$DynamicClassLoader |
0% | 0/28
0% | 0/6
| 4.0;4

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.plugin;
19 |  |
20 |  |
     import org.apache.any23.cli.Tool;
21 |  |
     import org.apache.any23.configuration.DefaultConfiguration;
22 |  |
     import org.apache.any23.extractor.ExtractorFactory;
23 |  |
     import org.apache.any23.extractor.ExtractorGroup;
24 |  |
     import org.apache.any23.extractor.ExtractorRegistry;
25 |  |
     import org.apache.any23.util.FileUtils;
26 |  |
     import org.slf4j.Logger;
27 |  |
     import org.slf4j.LoggerFactory;
28 |  |
29 |  |
     import java.io.File;
30 |  |
     import java.io.FileInputStream;
31 |  |
     import java.io.FilenameFilter;
32 |  |
     import java.io.IOException;
33 |  |
     import java.net.MalformedURLException;
34 |  |
     import java.net.URL;
35 |  |
     import java.net.URLClassLoader;
36 |  |
     import java.net.URLDecoder;
37 |  |
     import java.util.ArrayList;
38 |  |
     import java.util.HashSet;
39 |  |
     import java.util.List;
40 |  |
     import java.util.Set;
41 |  |
     import java.util.jar.JarEntry;
42 |  |
     import java.util.jar.JarInputStream;
43 |  |
44 |  |
     /**
45 |  |
      * The <i>Any23PluginManager</i> is responsible for inspecting
46 |  |
      * dynamically the classpath and retrieving useful classes.
47 |  |
      *
48 |  |
      * @author Michele Mostarda (mostarda@fbk.eu)
49 |  |
      */
50 | 0 |
     public class Any23PluginManager {
51 |  |
52 |  |
         /**
53 |  |
          * Any23 Command Line Interface package.
54 |  |
          */
55 | 0 |
         public static final String CLI_PACKAGE = Tool.class.getPackage().getName();
56 |  |
57 |  |
         /**
58 |  |
          * Any23 Plugins package.
59 |  |
          */
60 | 0 |
         public static final String PLUGINS_PACKAGE = ExtractorPlugin.class.getPackage().getName();
61 |  |
62 |  |
         /**
63 |  |
          * Property where look for plugins.
64 |  |
          */
65 |  |
         public static final String PLUGIN_DIRS_PROPERTY = "any23.plugin.dirs";
66 |  |
67 |  |
         /**
68 |  |
          * List separator for the string declaring the plugin list.
69 |  |
          */
70 |  |
         public static final String PLUGIN_DIRS_LIST_SEPARATOR = ":";
71 |  |
72 |  |
         /**
73 |  |
          * Internal logger.
74 |  |
          */
75 | 0 |
         private static final Logger logger = LoggerFactory.getLogger(Any23PluginManager.class);
76 |  |
77 |  |
         /**
78 |  |
          * Singleton lazy instance.
79 |  |
          */
80 |  |
         private static Any23PluginManager instance;
81 |  |
82 |  |
         /**
83 |  |
          * Internal class loader used to dynamically load classes.
84 |  |
          */
85 |  |
         private final DynamicClassLoader dynamicClassLoader;
86 |  |
87 |  |
         /**
88 |  |
          * @return a singleton instance of {@link Any23PluginManager}.
89 |  |
          */
90 |  |
         public static synchronized Any23PluginManager getInstance() {
91 | 0 |
             if(instance == null) {
92 | 0 |
                 instance = new Any23PluginManager();
93 |  |
             }
94 | 0 |
             return instance;
95 |  |
         }
96 |  |
97 |  |
         /**
98 |  |
          * Constructor.
99 |  |
          */
100 | 0 |
         private Any23PluginManager() {
101 | 0 |
             dynamicClassLoader = new DynamicClassLoader();
102 | 0 |
         }
103 |  |
104 |  |
         /**
105 |  |
          * Loads a <i>JAR</i> file in the classpath.
106 |  |
          *
107 |  |
          * @param jar the JAR file to be loaded.
108 |  |
          * @return <code>true</code> if the JAR is added for the first time to the classpath,
109 |  |
          *         <code>false</code> otherwise.
110 |  |
          * @throws MalformedURLException
111 |  |
          */
112 |  |
         public synchronized boolean loadJAR(File jar) {
113 | 0 |
             if(jar == null) throw new NullPointerException("jar file cannot be null.");
114 | 0 |
             if (!jar.isFile() && !jar.exists()) {
115 | 0 |
                 throw new IllegalArgumentException(
116 |  |
                         String.format("Invalid JAR [%s], must be an existing file.", jar.getAbsolutePath())
117 |  |
                 );
118 |  |
             }
119 | 0 |
             return dynamicClassLoader.addJAR(jar);
120 |  |
         }
121 |  |
122 |  |
         /**
123 |  |
          * Loads a list of <i>JAR</i>s in the classpath.
124 |  |
          *
125 |  |
          * @param jars list of JARs to be loaded.
126 |  |
          * @return list of exceptions raised during the loading.
127 |  |
          */
128 |  |
         public synchronized Throwable[] loadJARs(File... jars) {
129 | 0 |
             final List<Throwable> result = new ArrayList<Throwable>();
130 | 0 |
             for (File jar : jars) {
131 |  |
                 try {
132 | 0 |
                     loadJAR(jar);
133 | 0 |
                 } catch (Throwable t) {
134 | 0 |
                     result.add(
135 |  |
                             new IllegalArgumentException(
136 |  |
                                     String.format("Error while loading jar [%s]", jar.getAbsolutePath()),
137 |  |
                                     t
138 |  |
                             )
139 |  |
                     );
140 | 0 |
                 }
141 |  |
             }
142 | 0 |
             return result.toArray(new Throwable[result.size()]);
143 |  |
         }
144 |  |
145 |  |
         /**
146 |  |
          * Loads a <i>classes</i> directory in the classpath.
147 |  |
          *
148 |  |
          * @param classDir the directory to be loaded.
149 |  |
          * @return <code>true</code> if the directory is added for the first time to the classpath,
150 |  |
          *         <code>false</code> otherwise.
151 |  |
          */
152 |  |
         public synchronized boolean loadClassDir(File classDir) {
153 | 0 |
             if(classDir == null) throw new NullPointerException("classDir cannot be null.");
154 | 0 |
             if (!classDir.isDirectory() && !classDir.exists()) {
155 | 0 |
                 throw new IllegalArgumentException(
156 |  |
                         String.format("Invalid class dir [%s], must be an existing file.", classDir.getAbsolutePath())
157 |  |
                 );
158 |  |
             }
159 | 0 |
             return dynamicClassLoader.addClassDir(classDir);
160 |  |
         }
161 |  |
162 |  |
         /**
163 |  |
          * Loads a list of class dirs in the classpath.
164 |  |
          *
165 |  |
          * @param classDirs list of class dirs to be loaded.
166 |  |
          * @return  list of exceptions raised during the loading.
167 |  |
          */
168 |  |
         public synchronized Throwable[] loadClassDirs(File... classDirs) {
169 | 0 |
             final List<Throwable> result = new ArrayList<Throwable>();
170 | 0 |
             for (File classDir : classDirs) {
171 |  |
                 try {
172 | 0 |
                     loadClassDir(classDir);
173 | 0 |
                 } catch (Throwable t) {
174 | 0 |
                     result.add(
175 |  |
                             new IllegalArgumentException(
176 |  |
                                     String.format("Error while loading class dir [%s]", classDir.getAbsolutePath()),
177 |  |
                                     t
178 |  |
                             )
179 |  |
                     );
180 | 0 |
                 }
181 |  |
             }
182 | 0 |
             return result.toArray(new Throwable[result.size()]);
183 |  |
         }
184 |  |
185 |  |
         /**
186 |  |
          * Loads all the JARs detected in a given directory.
187 |  |
          *
188 |  |
          * @param jarDir directory containing the JARs to be loaded.
189 |  |
          * @return <code>true</code> if all JARs in dir are loaded.
190 |  |
          */
191 |  |
         public synchronized boolean loadJARDir(File jarDir) {
192 | 0 |
             if(jarDir == null)
193 | 0 |
                 throw new NullPointerException("JAR dir must be not null.");
194 | 0 |
             if(  ! jarDir.exists() )
195 | 0 |
                 throw new IllegalArgumentException("Given directory doesn't exist:" + jarDir.getAbsolutePath());
196 | 0 |
             if(! jarDir.isDirectory() )
197 | 0 |
                 throw new IllegalArgumentException(
198 |  |
                         "given file exists and it is not a directory: " + jarDir.getAbsolutePath()
199 |  |
                 );
200 | 0 |
             boolean loaded = true;
201 | 0 |
             for (File jarFile : jarDir.listFiles(
202 | 0 |
                     new FilenameFilter() {
203 |  |
                         @Override
204 |  |
                         public boolean accept(File dir, String name) {
205 | 0 |
                             return name.endsWith(".jar");
206 |  |
                         }
207 |  |
                     })
208 |  |
             ) {
209 | 0 |
                 loaded &= loadJAR(jarFile);
210 |  |
             }
211 | 0 |
             return loaded;
212 |  |
         }
213 |  |
214 |  |
         /**
215 |  |
          * Loads a generic list of files, trying to determine the type of every file.
216 |  |
          *
217 |  |
          * @param files list of files to be loaded.
218 |  |
          * @return list of errors occurred during loading.
219 |  |
          */
220 |  |
         public synchronized Throwable[] loadFiles(File... files) {
221 | 0 |
             final List<Throwable> errors = new ArrayList<Throwable>();
222 | 0 |
             for(File file : files) {
223 |  |
                 try {
224 | 0 |
                     if (file.isFile() && file.getName().endsWith(".jar")) {
225 | 0 |
                         loadJAR(file);
226 | 0 |
                     } else if (file.isDirectory()) {
227 | 0 |
                         if (file.getName().endsWith("classes")) {
228 | 0 |
                             loadClassDir(file);
229 |  |
                         } else {
230 | 0 |
                             loadJARDir(file);
231 |  |
                         }
232 |  |
                     } else {
233 | 0 |
                         throw new IllegalArgumentException("Cannot handle file " + file.getAbsolutePath());
234 |  |
                     }
235 | 0 |
                 } catch (Throwable t) {
236 | 0 |
                     errors.add(t);
237 | 0 |
                 }
238 |  |
             }
239 | 0 |
             return errors.toArray(new Throwable[errors.size()]);
240 |  |
         }
241 |  |
242 |  |
         /**
243 |  |
          * Returns all classes within the specified <code>packageName</code> satisfying the given class
244 |  |
          * <code>filter</code>. The search is performed on the static classpath (the one the application
245 |  |
          * started with) and the dynamic classpath (the one specified using the load methods).
246 |  |
          *
247 |  |
          * @param <T> type of filtered class.
248 |  |
          * @param packageName package name to look at classes, if <code>null</code> all packages will be found.
249 |  |
          * @param filter class filter to select classes, if <code>null</code> all classes will be returned.
250 |  |
          * @return list of matching classes.
251 |  |
          * @throws IOException
252 |  |
          */
253 |  |
         public synchronized <T> Set<Class<T>> getClassesInPackage(final String packageName, final ClassFilter filter)
254 |  |
         throws IOException {
255 | 0 |
             final Set<Class<T>> result = new HashSet<Class<T>>();
256 | 0 |
             loadClassesInPackageFromClasspath(packageName, filter, result);
257 | 0 |
             for(File jar : dynamicClassLoader.jars) {
258 | 0 |
                 loadClassesInPackageFromJAR(jar, packageName, filter, result);
259 |  |
             }
260 | 0 |
             for(File dir : dynamicClassLoader.dirs) {
261 | 0 |
                 loadClassesInPackageFromDir(dir, packageName, filter, result);
262 |  |
             }
263 | 0 |
             return result;
264 |  |
         }
265 |  |
266 |  |
         /**
267 |  |
          * Returns the list of all the {@link Tool} classes declared within the classpath.
268 |  |
          *
269 |  |
          * @return not <code>null</code> list of tool classes.
270 |  |
          * @throws IOException
271 |  |
          */
272 |  |
         public synchronized Class<Tool>[] getTools() throws IOException {
273 | 0 |
             final Set<Class<Tool>> result = getClassesInPackage(
274 |  |
                     CLI_PACKAGE,
275 | 0 |
                     new ClassFilter() {
276 |  |
                         @Override
277 |  |
                         public boolean accept(Class clazz) {
278 | 0 |
                             return !clazz.equals(Tool.class) && Tool.class.isAssignableFrom(clazz);
279 |  |
                         }
280 |  |
                     }
281 |  |
             );
282 | 0 |
             return result.toArray( new Class[result.size()] );
283 |  |
         }
284 |  |
285 |  |
         /**
286 |  |
          * List of {@link ExtractorPlugin} classes declared within the classpath.
287 |  |
          *
288 |  |
          * @return not <code>null</code> list of plugin classes.
289 |  |
          * @throws IOException
290 |  |
          */
291 |  |
         public synchronized Class<ExtractorPlugin>[] getPlugins() throws IOException {
292 | 0 |
             final Set<Class<ExtractorPlugin>> result = getClassesInPackage(
293 |  |
                     PLUGINS_PACKAGE,
294 | 0 |
                     new ClassFilter() {
295 |  |
                         @Override
296 |  |
                         public boolean accept(Class clazz) {
297 | 0 |
                             return !clazz.equals(ExtractorPlugin.class) && ExtractorPlugin.class.isAssignableFrom(clazz);
298 |  |
                         }
299 |  |
                     }
300 |  |
             );
301 | 0 |
             return result.toArray( new Class[result.size()] );
302 |  |
         }
303 |  |
304 |  |
         /**
305 |  |
          * Configures a new list of extractors containing the extractors declared in <code>initialExtractorGroup</code>
306 |  |
          * and also the extractors detected in classpath specified by <code>pluginLocations</code>.
307 |  |
          *
308 |  |
          * @param initialExtractorGroup initial list of extractors.
309 |  |
          * @param pluginLocations
310 |  |
          * @return full list of extractors.
311 |  |
          * @throws java.io.IOException
312 |  |
          * @throws IllegalAccessException
313 |  |
          * @throws InstantiationException
314 |  |
          */
315 |  |
         public synchronized ExtractorGroup configureExtractors(
316 |  |
                 final ExtractorGroup initialExtractorGroup,
317 |  |
                 final File... pluginLocations
318 |  |
         ) throws IOException, IllegalAccessException, InstantiationException {
319 | 0 |
             if(initialExtractorGroup == null) throw new NullPointerException("inExtractorGroup cannot be null");
320 |  |
321 | 0 |
             final StringBuilder report = new StringBuilder();
322 |  |
             try {
323 | 0 |
                 report.append("\nLoading plugins from locations {\n");
324 | 0 |
                 for (File pluginLocation : pluginLocations) {
325 | 0 |
                     report.append(pluginLocation.getAbsolutePath()).append('\n');
326 |  |
                 }
327 | 0 |
                 report.append("}\n");
328 |  |
329 | 0 |
                 final Throwable[] errors = loadFiles(pluginLocations);
330 | 0 |
                 if (errors.length > 0) {
331 | 0 |
                     report.append("The following errors occurred while loading plugins {\n");
332 | 0 |
                     for (Throwable error : errors) {
333 | 0 |
                         report.append(error);
334 | 0 |
                         report.append("\n\n\n");
335 |  |
                     }
336 | 0 |
                     report.append("}\n");
337 |  |
                 }
338 |  |
339 | 0 |
                 final Class<ExtractorPlugin>[] extractorPluginClasses = getPlugins();
340 | 0 |
                 if (extractorPluginClasses.length == 0) {
341 | 0 |
                     report.append("\n=== No plugins have been found.===\n");
342 | 0 |
                     return initialExtractorGroup;
343 |  |
                 } else {
344 | 0 |
                     report.append("\nThe following plugins have been found {\n");
345 | 0 |
                     final List<ExtractorFactory<?>> newFactoryList = new ArrayList<ExtractorFactory<?>>();
346 | 0 |
                     for (Class<ExtractorPlugin> extractorPluginClass : extractorPluginClasses) {
347 | 0 |
                         final ExtractorPlugin extractorPlugin = extractorPluginClass.newInstance();
348 | 0 |
                         newFactoryList.add(extractorPlugin.getExtractorFactory());
349 | 0 |
                         report.append(
350 |  |
                                 extractorPlugin.getExtractorFactory().getExtractorName()
351 |  |
                         ).append("\n");
352 |  |
                     }
353 | 0 |
                     report.append("}\n");
354 |  |
355 | 0 |
                     for(ExtractorFactory extractorFactory : initialExtractorGroup) {
356 | 0 |
                         newFactoryList.add(extractorFactory);
357 |  |
                     }
358 | 0 |
                     return new ExtractorGroup(newFactoryList);
359 |  |
                 }
360 |  |
             } finally {
361 | 0 |
                 logger.info(report.toString());
362 |  |
             }
363 |  |
         }
364 |  |
365 |  |
         /**
366 |  |
          * Configures a new list of extractors containing the extractors declared in <code>initialExtractorGroup</code>
367 |  |
          * and also the extractors detected in classpath specified by the default configuration.
368 |  |
          *
369 |  |
          * @param initialExtractorGroup initial list of extractors.
370 |  |
          * @return full list of extractors.
371 |  |
          * @throws IOException
372 |  |
          * @throws InstantiationException
373 |  |
          * @throws IllegalAccessException
374 |  |
          */
375 |  |
         public synchronized ExtractorGroup configureExtractors(ExtractorGroup initialExtractorGroup)
376 |  |
         throws IOException, InstantiationException, IllegalAccessException {
377 | 0 |
             final String pluginDirs = DefaultConfiguration.singleton().getPropertyOrFail(PLUGIN_DIRS_PROPERTY);
378 | 0 |
             final File[] pluginLocations = getPluginLocations(pluginDirs);
379 | 0 |
             return configureExtractors(initialExtractorGroup, pluginLocations);
380 |  |
         }
381 |  |
382 |  |
         /**
383 |  |
          * Returns an extractor group containing both the default extractors declared by the
384 |  |
          * {@link org.apache.any23.extractor.ExtractorRegistry} and the {@link ExtractorPlugin}s.
385 |  |
          *
386 |  |
          * @param pluginLocations optional list of plugin locations.
387 |  |
          * @return a not <code>null</code> and not empty extractor group.
388 |  |
          * @throws java.io.IOException
389 |  |
          * @throws IllegalAccessException
390 |  |
          * @throws InstantiationException
391 |  |
          */
392 |  |
         public synchronized ExtractorGroup getApplicableExtractors(File... pluginLocations)
393 |  |
         throws IOException, IllegalAccessException, InstantiationException {
394 | 0 |
             final ExtractorGroup defaultExtractors = ExtractorRegistry.getInstance().getExtractorGroup();
395 | 0 |
             return configureExtractors(defaultExtractors, pluginLocations);
396 |  |
         }
397 |  |
398 |  |
         /**
399 |  |
          * Filters classes by criteria within a <i>JAR</i>.
400 |  |
          *
401 |  |
          * @param jarFile file addressing the JAR.
402 |  |
          * @param packageName name of package to scan.
403 |  |
          * @param filter filter class, all returned classes must extend the specified one.
404 |  |
          * @param result list for writing result.
405 |  |
          * @throws java.io.IOException
406 |  |
          */
407 |  |
         protected <T> void loadClassesInPackageFromJAR(
408 |  |
                 File jarFile,
409 |  |
                 String packageName,
410 |  |
                 ClassFilter filter,
411 |  |
                 Set<Class<T>> result
412 |  |
         ) throws IOException {
413 | 0 |
             loadJAR(jarFile);
414 | 0 |
             packageName = packageName.replaceAll("\\.", "/");
415 | 0 |
             JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile));
416 |  |
             JarEntry jarEntry;
417 |  |
             while (true) {
418 |  |
                 try {
419 | 0 |
                     jarEntry = jarInputStream.getNextJarEntry();
420 | 0 |
                 } catch (IOException ioe) {
421 | 0 |
                     throw new IllegalStateException("Error while accessing JAR.", ioe);
422 | 0 |
                 }
423 | 0 |
                 if (jarEntry == null) {
424 | 0 |
                     break;
425 |  |
                 }
426 | 0 |
                 final String jarEntryName = jarEntry.getName();
427 | 0 |
                 if (jarEntryName.startsWith(packageName) && isValidClassName(jarEntryName)) {
428 | 0 |
                     final String classEntry = jarEntryName.replaceAll("/", "\\.");
429 | 0 |
                     final String classStr = classEntry.substring(0, classEntry.indexOf(".class"));
430 |  |
                     final Class clazz;
431 |  |
                     try {
432 | 0 |
                         clazz = Class.forName(classStr, true, dynamicClassLoader);
433 | 0 |
                     } catch (ClassNotFoundException cnfe) {
434 | 0 |
                         throw new IllegalStateException("Error while creating class.", cnfe);
435 | 0 |
                     }
436 | 0 |
                     if (filter == null || filter.accept(clazz)) {
437 | 0 |
                         result.add(clazz);
438 |  |
                     }
439 |  |
                 }
440 | 0 |
             }
441 | 0 |
         }
442 |  |
443 |  |
         /**
444 |  |
          * Filters classes by criteria within a <i>class dir</i>.
445 |  |
          *
446 |  |
          * @param classDir class directory.
447 |  |
          * @param packageName name of package to scan.
448 |  |
          * @param filter filter class, all returned classes must extend the specified one.
449 |  |
          * @param result list for writing result.
450 |  |
          * @param <T> class types.
451 |  |
          * @throws MalformedURLException
452 |  |
          */
453 |  |
         protected <T> void loadClassesInPackageFromDir(
454 |  |
                 File classDir,
455 |  |
                 final String packageName,
456 |  |
                 final ClassFilter filter,
457 |  |
                 Set<Class<T>> result
458 |  |
         ) throws MalformedURLException {
459 | 0 |
             if(packageName != null && packageName.trim().length() == 0) {
460 | 0 |
                 throw new IllegalArgumentException("Invalid packageName filter '" + packageName + "'");
461 |  |
             }
462 | 0 |
             loadClassDir(classDir);
463 | 0 |
             final int PREFIX_LENGTH = classDir.getAbsolutePath().length();
464 | 0 |
             File[] classFiles = FileUtils.listFilesRecursively(
465 |  |
                     classDir,
466 | 0 |
                     new FilenameFilter() {
467 |  |
                         @Override
468 |  |
                         public boolean accept(File dir, String name) {
469 | 0 |
                             if (!isValidClassName(name)) return false;
470 | 0 |
                             if (packageName == null) return true;
471 | 0 |
                             final String absolutePath = dir.getAbsolutePath();
472 | 0 |
                             if (absolutePath.length() <= PREFIX_LENGTH) return false;
473 | 0 |
                             return
474 |  |
                                     absolutePath
475 |  |
                                             .substring(PREFIX_LENGTH + 1)
476 |  |
                                             .replaceAll("/", "\\.")
477 |  |
                                             .startsWith(packageName);
478 |  |
                         }
479 |  |
                     }
480 |  |
             );
481 | 0 |
             final int classDirPathLength = classDir.getAbsolutePath().length();
482 | 0 |
             for (File classFile : classFiles) {
483 |  |
                 final Class clazz;
484 |  |
                 try {
485 | 0 |
                     String className =
486 |  |
                             classFile
487 |  |
                                     .getAbsolutePath()
488 |  |
                                     .substring(classDirPathLength + 1);
489 | 0 |
                     className = className.substring(0, className.length() - ".class".length()).replaceAll("/", "\\.");
490 | 0 |
                     clazz = Class.forName(className, true, dynamicClassLoader);
491 | 0 |
                 } catch (ClassNotFoundException cnfe) {
492 | 0 |
                     throw new IllegalStateException("Error while instantiating class.", cnfe);
493 | 0 |
                 }
494 | 0 |
                 if (filter == null || filter.accept(clazz)) {
495 | 0 |
                     result.add(clazz);
496 |  |
                 }
497 |  |
             }
498 | 0 |
         }
499 |  |
500 |  |
         /**
501 |  |
          * Filters classes by criteria within the initialization <i>classpath</i>.
502 |  |
          *
503 |  |
          * @param packageName name of package to scan.
504 |  |
          * @param filter filter class, all returned classes must extend the specified one.
505 |  |
          * @param result list for writing result.
506 |  |
          * @param <T>
507 |  |
          * @throws IOException
508 |  |
          */
509 |  |
         protected <T> void loadClassesInPackageFromClasspath(
510 |  |
                 final String packageName,
511 |  |
                 final ClassFilter filter,
512 |  |
                 Set<Class<T>> result
513 |  |
         ) throws IOException {
514 | 0 |
             final String[] classpathEntries = getClasspathEntries();
515 | 0 |
             for (String classPathEntry : classpathEntries) {
516 | 0 |
                 if(classPathEntry.trim().length() == 0) continue;
517 | 0 |
                 final File codePath = new File(URLDecoder.decode(classPathEntry, "UTF-8"));
518 | 0 |
                 if( ! codePath.exists() ) continue;
519 | 0 |
                 if (codePath.isDirectory()) {
520 | 0 |
                     loadClassesInPackageFromDir(codePath, packageName, filter, result);
521 |  |
                 } else {
522 | 0 |
                     loadClassesInPackageFromJAR(codePath, packageName, filter, result);
523 |  |
                 }
524 |  |
             }
525 | 0 |
         }
526 |  |
527 |  |
         /**
528 |  |
          * @return the classpath entries.
529 |  |
          */
530 |  |
         private String[] getClasspathEntries() {
531 | 0 |
             final String classpath          = System.getProperty("java.class.path");
532 | 0 |
             assert classpath != null : "Class path is null.";
533 | 0 |
             final String classpathSeparator = System.getProperty("path.separator");
534 | 0 |
             assert classpathSeparator != null : "Class path separator is null.";
535 | 0 |
             return classpath.split("\\" + classpathSeparator);
536 |  |
         }
537 |  |
538 |  |
         /**
539 |  |
          * Checks if the class name is valid.
540 |  |
          *
541 |  |
          * @param clazzName
542 |  |
          * @return
543 |  |
          */
544 |  |
         private boolean isValidClassName(String clazzName) {
545 | 0 |
             return clazzName.endsWith(".class") && ! clazzName.contains("$");
546 |  |
         }
547 |  |
548 |  |
         /**
549 |  |
          * Converts a column separated list of dirs in a list of files.
550 |  |
          *
551 |  |
          * @param pluginDirsList
552 |  |
          * @return
553 |  |
          */
554 |  |
         private File[] getPluginLocations(String pluginDirsList) {
555 | 0 |
             final String[] locationsStr = pluginDirsList.split(PLUGIN_DIRS_LIST_SEPARATOR);
556 | 0 |
             final List<File> locations = new ArrayList<File>();
557 | 0 |
             for(String locationStr : locationsStr) {
558 | 0 |
                 final File location = new File(locationStr);
559 | 0 |
                 if( ! location.exists()) {
560 | 0 |
                     throw new IllegalArgumentException(
561 |  |
                             String.format("Plugin location '%s' cannot be found.", locationStr)
562 |  |
                     );
563 |  |
                 }
564 | 0 |
                 locations.add(location);
565 |  |
             }
566 | 0 |
             return locations.toArray(new File[locations.size()]);
567 |  |
         }
568 |  |
569 |  |
         /**
570 |  |
          * Dynamic local file class loader.
571 |  |
          */
572 | 0 |
         private class DynamicClassLoader extends URLClassLoader {
573 |  |
574 | 0 |
             private final Set<String> addedURLs = new HashSet<String>();
575 |  |
576 |  |
             private final List<File> jars;
577 |  |
578 |  |
             private final List<File> dirs;
579 |  |
580 | 0 |
             public DynamicClassLoader(URL[] urls) {
581 | 0 |
                 super(urls);
582 | 0 |
                 jars = new ArrayList<File>();
583 | 0 |
                 dirs = new ArrayList<File>();
584 | 0 |
             }
585 |  |
586 |  |
             public DynamicClassLoader() {
587 | 0 |
                 this(new URL[0]);
588 | 0 |
             }
589 |  |
590 |  |
             public boolean addClassDir(File classDir) {
591 | 0 |
                 final String urlPath = "file://" + classDir.getAbsolutePath() + "/";
592 |  |
                 try {
593 | 0 |
                     if( addURL(urlPath) ) {
594 | 0 |
                         dirs.add(classDir);
595 | 0 |
                         return true;
596 |  |
                     }
597 | 0 |
                     return false;
598 | 0 |
                 } catch (MalformedURLException murle) {
599 | 0 |
                     throw new RuntimeException("Invalid dir URL.", murle);
600 |  |
                 }
601 |  |
             }
602 |  |
603 |  |
             public boolean addJAR(File jar) {
604 | 0 |
                 final String urlPath = "jar:file://" + jar.getAbsolutePath() + "!/";
605 |  |
                 try {
606 | 0 |
                     if (addURL(urlPath)) {
607 | 0 |
                         jars.add(jar);
608 | 0 |
                         return true;
609 |  |
                     }
610 | 0 |
                     return false;
611 | 0 |
                 } catch (MalformedURLException murle) {
612 | 0 |
                     throw new RuntimeException("Invalid JAR URL.", murle);
613 |  |
                 }
614 |  |
             }
615 |  |
616 |  |
             private boolean addURL(String urlPath) throws MalformedURLException {
617 | 0 |
                 if(addedURLs.contains(urlPath)) {
618 | 0 |
                     return false;
619 |  |
                 }
620 | 0 |
                 super.addURL(new URL(urlPath));
621 | 0 |
                 addedURLs.add(urlPath);
622 | 0 |
                 return true;
623 |  |
             }
624 |  |
         }
625 |  |
626 |  |
     }