• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }