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 PluginLoader(PluginSwitch pluginSwitch)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 */ withAlias(String name, String type)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") loadPlugin(final Class<T> pluginType, String defaultPluginClassName)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 */ loadImpl(Class<T> service)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