SimplePerRealmReloadingClassLoader.java revision 2637d96c202372854a7c71466ddcc6e90fc4fc53
1package org.mockitoutil;
2
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.net.URLClassLoader;
6import java.util.HashMap;
7import java.util.Map;
8import java.util.concurrent.Callable;
9
10/**
11 * Custom classloader to load classes in hierarchic realm.
12 *
13 * Each class can be reloaded in the realm if the LoadClassPredicate says so.
14 */
15public class SimplePerRealmReloadingClassLoader extends URLClassLoader {
16
17    private final Map<String,Class<?>> classHashMap = new HashMap<String, Class<?>>();
18    private ReloadClassPredicate reloadClassPredicate;
19
20    public SimplePerRealmReloadingClassLoader(ReloadClassPredicate reloadClassPredicate) {
21        super(getPossibleClassPathsUrls());
22        this.reloadClassPredicate = reloadClassPredicate;
23    }
24
25    public SimplePerRealmReloadingClassLoader(ClassLoader parentClassLoader, ReloadClassPredicate reloadClassPredicate) {
26        super(getPossibleClassPathsUrls(), parentClassLoader);
27        this.reloadClassPredicate = reloadClassPredicate;
28    }
29
30    private static URL[] getPossibleClassPathsUrls() {
31        return new URL[]{
32                obtainClassPath(),
33                obtainClassPath("org.mockito.Mockito"),
34                obtainClassPath("net.bytebuddy.ByteBuddy")
35        };
36    }
37
38    private static URL obtainClassPath() {
39        String className = SimplePerRealmReloadingClassLoader.class.getName();
40        return obtainClassPath(className);
41    }
42
43    private static URL obtainClassPath(String className) {
44        String path = className.replace('.', '/') + ".class";
45        String url = SimplePerRealmReloadingClassLoader.class.getClassLoader().getResource(path).toExternalForm();
46
47        try {
48            return new URL(url.substring(0, url.length() - path.length()));
49        } catch (MalformedURLException e) {
50            throw new RuntimeException("Classloader couldn't obtain a proper classpath URL", e);
51        }
52    }
53
54
55
56    @Override
57    public Class<?> loadClass(String qualifiedClassName) throws ClassNotFoundException {
58        if(reloadClassPredicate.acceptReloadOf(qualifiedClassName)) {
59            // return customLoadClass(qualifiedClassName);
60//            Class<?> loadedClass = findLoadedClass(qualifiedClassName);
61            if(!classHashMap.containsKey(qualifiedClassName)) {
62                Class<?> foundClass = findClass(qualifiedClassName);
63                saveFoundClass(qualifiedClassName, foundClass);
64                return foundClass;
65            }
66
67            return classHashMap.get(qualifiedClassName);
68        }
69        return useParentClassLoaderFor(qualifiedClassName);
70    }
71
72    private void saveFoundClass(String qualifiedClassName, Class<?> foundClass) {
73        classHashMap.put(qualifiedClassName, foundClass);
74    }
75
76
77    private Class<?> useParentClassLoaderFor(String qualifiedName) throws ClassNotFoundException {
78        return super.loadClass(qualifiedName);
79    }
80
81
82    public Object doInRealm(String callableCalledInClassLoaderRealm) throws Exception {
83        ClassLoader current = Thread.currentThread().getContextClassLoader();
84        try {
85            Thread.currentThread().setContextClassLoader(this);
86            Object instance = this.loadClass(callableCalledInClassLoaderRealm).getConstructor().newInstance();
87            if (instance instanceof Callable) {
88                Callable<?> callableInRealm = (Callable<?>) instance;
89                return callableInRealm.call();
90            }
91        } finally {
92            Thread.currentThread().setContextClassLoader(current);
93        }
94        throw new IllegalArgumentException("qualified name '" + callableCalledInClassLoaderRealm + "' should represent a class implementing Callable");
95    }
96
97
98    public Object doInRealm(String callableCalledInClassLoaderRealm, Class<?>[] argTypes, Object[] args) throws Exception {
99        ClassLoader current = Thread.currentThread().getContextClassLoader();
100        try {
101            Thread.currentThread().setContextClassLoader(this);
102            Object instance = this.loadClass(callableCalledInClassLoaderRealm).getConstructor(argTypes).newInstance(args);
103            if (instance instanceof Callable) {
104                Callable<?> callableInRealm = (Callable<?>) instance;
105                return callableInRealm.call();
106            }
107        } finally {
108            Thread.currentThread().setContextClassLoader(current);
109        }
110
111        throw new IllegalArgumentException("qualified name '" + callableCalledInClassLoaderRealm + "' should represent a class implementing Callable");
112    }
113
114
115    public interface ReloadClassPredicate {
116        boolean acceptReloadOf(String qualifiedName);
117    }
118}
119