Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright (c) 2016 Mockito contributors
      3  * This program is made available under the terms of the MIT License.
      4  */
      5 package org.mockito.internal.configuration.plugins;
      6 
      7 import org.mockito.internal.util.collections.Iterables;
      8 import org.mockito.plugins.PluginSwitch;
      9 
     10 import java.io.IOException;
     11 import java.lang.reflect.InvocationHandler;
     12 import java.lang.reflect.Method;
     13 import java.lang.reflect.Proxy;
     14 import java.net.URL;
     15 import java.util.Enumeration;
     16 import java.util.HashMap;
     17 import java.util.Map;
     18 
     19 class PluginLoader {
     20 
     21     private final PluginSwitch pluginSwitch;
     22 
     23     private final Map<String, String> alias;
     24 
     25     public PluginLoader(PluginSwitch pluginSwitch) {
     26         this.pluginSwitch = pluginSwitch;
     27         this.alias = new HashMap<String, String>();
     28     }
     29 
     30     /**
     31      * Adds an alias for a class name to this plugin loader. Instead of the fully qualified type name,
     32      * the alias can be used as a convenience name for a known plugin.
     33      */
     34     PluginLoader withAlias(String name, String type) {
     35         alias.put(name, type);
     36         return this;
     37     }
     38 
     39     /**
     40      * Scans the classpath for given pluginType. If not found, default class is used.
     41      */
     42     @SuppressWarnings("unchecked")
     43     <T> T loadPlugin(final Class<T> pluginType, String defaultPluginClassName) {
     44         try {
     45             T plugin = loadImpl(pluginType);
     46             if (plugin != null) {
     47                 return plugin;
     48             }
     49 
     50             try {
     51                 // Default implementation. Use our own ClassLoader instead of the context
     52                 // ClassLoader, as the default implementation is assumed to be part of
     53                 // Mockito and may not be available via the context ClassLoader.
     54                 return pluginType.cast(Class.forName(defaultPluginClassName).newInstance());
     55             } catch (Exception e) {
     56                 throw new IllegalStateException("Internal problem occurred, please report it. " +
     57                         "Mockito is unable to load the default implementation of class that is a part of Mockito distribution. " +
     58                         "Failed to load " + pluginType, e);
     59             }
     60         } catch (final Throwable t) {
     61             return (T) Proxy.newProxyInstance(pluginType.getClassLoader(),
     62                     new Class<?>[]{pluginType},
     63                     new InvocationHandler() {
     64                         @Override
     65                         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     66                             throw new IllegalStateException("Could not initialize plugin: " + pluginType, t);
     67                         }
     68                     });
     69         }
     70     }
     71 
     72     /**
     73      * Equivalent to {@link java.util.ServiceLoader#load} but without requiring
     74      * Java 6 / Android 2.3 (Gingerbread).
     75      */
     76     private <T> T loadImpl(Class<T> service) {
     77         ClassLoader loader = Thread.currentThread().getContextClassLoader();
     78         if (loader == null) {
     79             loader = ClassLoader.getSystemClassLoader();
     80         }
     81         Enumeration<URL> resources;
     82         try {
     83             resources = loader.getResources("mockito-extensions/" + service.getName());
     84         } catch (IOException e) {
     85             throw new IllegalStateException("Failed to load " + service, e);
     86         }
     87 
     88         try {
     89             String foundPluginClass = new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));
     90             if (foundPluginClass != null) {
     91                 String aliasType = alias.get(foundPluginClass);
     92                 if (aliasType != null) {
     93                     foundPluginClass = aliasType;
     94                 }
     95                 Class<?> pluginClass = loader.loadClass(foundPluginClass);
     96                 Object plugin = pluginClass.newInstance();
     97                 return service.cast(plugin);
     98             }
     99             return null;
    100         } catch (Exception e) {
    101             throw new IllegalStateException(
    102                     "Failed to load " + service + " implementation declared in " + resources, e);
    103         }
    104     }
    105 }
    106