1 /* 2 * Copyright (c) 2007 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.configuration; 6 7 import org.mockito.configuration.IMockitoConfiguration; 8 import org.mockito.exceptions.base.MockitoException; 9 import org.mockito.exceptions.misusing.MockitoConfigurationException; 10 import org.mockito.plugins.MockMaker; 11 import org.mockito.plugins.StackTraceCleanerProvider; 12 13 import java.io.*; 14 import java.net.URL; 15 import java.util.ArrayList; 16 import java.util.Collections; 17 import java.util.Enumeration; 18 import java.util.List; 19 20 /** 21 * Loads configuration or extension points available in the classpath. 22 * 23 * <p> 24 * <ul> 25 * <li> 26 * Can load the mockito configuration. The user who want to provide his own mockito configuration 27 * should write the class <code>org.mockito.configuration.MockitoConfiguration</code> that implements 28 * {@link IMockitoConfiguration}. For example : 29 * <pre class="code"><code class="java"> 30 * package org.mockito.configuration; 31 * 32 * //... 33 * 34 * public class MockitoConfiguration implements IMockitoConfiguration { 35 * boolean enableClassCache() { return false; } 36 * 37 * // ... 38 * } 39 * </code></pre> 40 * </li> 41 * <li> 42 * Can load available mockito extensions. Currently Mockito only have one extension point the 43 * {@link MockMaker}. This extension point allows a user to provide his own bytecode engine to build mocks. 44 * <br>Suppose you wrote an extension to create mocks with some <em>Awesome</em> library, in order to tell 45 * Mockito to use it you need to put in your classpath 46 * <ol style="list-style-type: lower-alpha"> 47 * <li>The implementation itself, for example <code>org.awesome.mockito.AwesomeMockMaker</code>.</li> 48 * <li>A file named <code>org.mockito.plugins.MockMaker</code> in a folder named 49 * <code>mockito-extensions</code>, the content of this file need to have <strong>one</strong> line with 50 * the qualified name <code>org.awesome.mockito.AwesomeMockMaker</code>.</li> 51 * </ol> 52 * </li> 53 * </ul> 54 * </p> 55 */ 56 public class ClassPathLoader { 57 private static final String DEFAULT_MOCK_MAKER_CLASS = 58 "org.mockito.internal.creation.CglibMockMaker"; 59 private static final String DEFAULT_STACK_TRACE_CLEANER_PROVIDER_CLASS = 60 "org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleanerProvider"; 61 public static final String MOCKITO_CONFIGURATION_CLASS_NAME = "org.mockito.configuration.MockitoConfiguration"; 62 63 private static final MockMaker mockMaker = findPlatformMockMaker(); 64 private static final StackTraceCleanerProvider stackTraceCleanerProvider = 65 findPlatformStackTraceCleanerProvider(); 66 67 /** 68 * @return configuration loaded from classpath or null 69 */ 70 @SuppressWarnings({"unchecked"}) loadConfiguration()71 public IMockitoConfiguration loadConfiguration() { 72 //Trying to get config from classpath 73 Class configClass; 74 try { 75 configClass = (Class) Class.forName(MOCKITO_CONFIGURATION_CLASS_NAME); 76 } catch (ClassNotFoundException e) { 77 //that's ok, it means there is no global config, using default one. 78 return null; 79 } 80 81 try { 82 return (IMockitoConfiguration) configClass.newInstance(); 83 } catch (ClassCastException e) { 84 throw new MockitoConfigurationException("MockitoConfiguration class must implement " + IMockitoConfiguration.class.getName() + " interface.", e); 85 } catch (Exception e) { 86 throw new MockitoConfigurationException("Unable to instantiate " + MOCKITO_CONFIGURATION_CLASS_NAME +" class. Does it have a safe, no-arg constructor?", e); 87 } 88 } 89 90 /** 91 * Returns the implementation of the mock maker available for the current runtime. 92 * 93 * <p>Returns {@link org.mockito.internal.creation.CglibMockMaker} if no 94 * {@link MockMaker} extension exists or is visible in the current classpath.</p> 95 */ getMockMaker()96 public static MockMaker getMockMaker() { 97 return mockMaker; 98 } 99 getStackTraceCleanerProvider()100 public static StackTraceCleanerProvider getStackTraceCleanerProvider() { 101 //TODO we should throw some sensible exception if this is null. 102 return stackTraceCleanerProvider; 103 } 104 105 /** 106 * Scans the classpath to find a mock maker plugin if one is available, 107 * allowing mockito to run on alternative platforms like Android. 108 */ findPlatformMockMaker()109 static MockMaker findPlatformMockMaker() { 110 return findPluginImplementation(MockMaker.class, DEFAULT_MOCK_MAKER_CLASS); 111 } 112 findPlatformStackTraceCleanerProvider()113 static StackTraceCleanerProvider findPlatformStackTraceCleanerProvider() { 114 return findPluginImplementation( 115 StackTraceCleanerProvider.class, DEFAULT_STACK_TRACE_CLEANER_PROVIDER_CLASS); 116 } 117 findPluginImplementation(Class<T> pluginType, String defaultPluginClassName)118 static <T> T findPluginImplementation(Class<T> pluginType, String defaultPluginClassName) { 119 for (T plugin : loadImplementations(pluginType)) { 120 return plugin; // return the first one service loader finds (if any) 121 } 122 123 try { 124 // Default implementation. Use our own ClassLoader instead of the context 125 // ClassLoader, as the default implementation is assumed to be part of 126 // Mockito and may not be available via the context ClassLoader. 127 return pluginType.cast(Class.forName(defaultPluginClassName).newInstance()); 128 } catch (Exception e) { 129 throw new MockitoException("Internal problem occurred, please report it. " + 130 "Mockito is unable to load the default implementation of class that is a part of Mockito distribution. " + 131 "Failed to load " + pluginType, e); 132 } 133 } 134 135 /** 136 * Equivalent to {@link java.util.ServiceLoader#load} but without requiring 137 * Java 6 / Android 2.3 (Gingerbread). 138 */ loadImplementations(Class<T> service)139 static <T> List<T> loadImplementations(Class<T> service) { 140 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 141 if (loader == null) { 142 loader = ClassLoader.getSystemClassLoader(); 143 } 144 Enumeration<URL> resources; 145 try { 146 resources = loader.getResources("mockito-extensions/" + service.getName()); 147 } catch (IOException e) { 148 throw new MockitoException("Failed to load " + service, e); 149 } 150 151 List<T> result = new ArrayList<T>(); 152 for (URL resource : Collections.list(resources)) { 153 InputStream in = null; 154 try { 155 in = resource.openStream(); 156 for (String line : readerToLines(new InputStreamReader(in, "UTF-8"))) { 157 String name = stripCommentAndWhitespace(line); 158 if (name.length() != 0) { 159 result.add(service.cast(loader.loadClass(name).newInstance())); 160 } 161 } 162 } catch (Exception e) { 163 throw new MockitoConfigurationException( 164 "Failed to load " + service + " using " + resource, e); 165 } finally { 166 closeQuietly(in); 167 } 168 } 169 return result; 170 } 171 readerToLines(Reader reader)172 static List<String> readerToLines(Reader reader) throws IOException { 173 List<String> result = new ArrayList<String>(); 174 BufferedReader lineReader = new BufferedReader(reader); 175 String line; 176 while ((line = lineReader.readLine()) != null) { 177 result.add(line); 178 } 179 return result; 180 } 181 stripCommentAndWhitespace(String line)182 static String stripCommentAndWhitespace(String line) { 183 int hash = line.indexOf('#'); 184 if (hash != -1) { 185 line = line.substring(0, hash); 186 } 187 return line.trim(); 188 } 189 closeQuietly(InputStream in)190 private static void closeQuietly(InputStream in) { 191 if (in != null) { 192 try { 193 in.close(); 194 } catch (IOException ignored) { 195 } 196 } 197 } 198 }