Coverage Report - org.apache.any23.plugin.Any23PluginManager
 
Classes in this File Line Coverage Branch Coverage Complexity
Any23PluginManager
0%
0/159
0%
0/98
4
Any23PluginManager$1
0%
0/2
N/A
4
Any23PluginManager$2
0%
0/2
0%
0/4
4
Any23PluginManager$3
0%
0/2
0%
0/4
4
Any23PluginManager$4
0%
0/6
0%
0/6
4
Any23PluginManager$DynamicClassLoader
0%
0/28
0%
0/6
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  
 }