/* * Copyright (c) 2016 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.configuration.plugins; import org.mockito.internal.util.collections.Iterables; import org.mockito.plugins.PluginSwitch; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; class PluginLoader { private final PluginSwitch pluginSwitch; private final Map<String, String> alias; public PluginLoader(PluginSwitch pluginSwitch) { this.pluginSwitch = pluginSwitch; this.alias = new HashMap<String, String>(); } /** * Adds an alias for a class name to this plugin loader. Instead of the fully qualified type name, * the alias can be used as a convenience name for a known plugin. */ PluginLoader withAlias(String name, String type) { alias.put(name, type); return this; } /** * Scans the classpath for given pluginType. If not found, default class is used. */ @SuppressWarnings("unchecked") <T> T loadPlugin(final Class<T> pluginType, String defaultPluginClassName) { try { T plugin = loadImpl(pluginType); if (plugin != null) { return plugin; } try { // Default implementation. Use our own ClassLoader instead of the context // ClassLoader, as the default implementation is assumed to be part of // Mockito and may not be available via the context ClassLoader. return pluginType.cast(Class.forName(defaultPluginClassName).newInstance()); } catch (Exception e) { throw new IllegalStateException("Internal problem occurred, please report it. " + "Mockito is unable to load the default implementation of class that is a part of Mockito distribution. " + "Failed to load " + pluginType, e); } } catch (final Throwable t) { return (T) Proxy.newProxyInstance(pluginType.getClassLoader(), new Class<?>[]{pluginType}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { throw new IllegalStateException("Could not initialize plugin: " + pluginType, t); } }); } } /** * Equivalent to {@link java.util.ServiceLoader#load} but without requiring * Java 6 / Android 2.3 (Gingerbread). */ private <T> T loadImpl(Class<T> service) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } Enumeration<URL> resources; try { resources = loader.getResources("mockito-extensions/" + service.getName()); } catch (IOException e) { throw new IllegalStateException("Failed to load " + service, e); } try { String foundPluginClass = new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources)); if (foundPluginClass != null) { String aliasType = alias.get(foundPluginClass); if (aliasType != null) { foundPluginClass = aliasType; } Class<?> pluginClass = loader.loadClass(foundPluginClass); Object plugin = pluginClass.newInstance(); return service.cast(plugin); } return null; } catch (Exception e) { throw new IllegalStateException( "Failed to load " + service + " implementation declared in " + resources, e); } } }