Home | History | Annotate | Download | only in mockitoutil
      1 package org.mockitoutil;
      2 
      3 import java.io.ByteArrayInputStream;
      4 import java.io.File;
      5 import java.io.IOException;
      6 import java.io.InputStream;
      7 import java.lang.reflect.Field;
      8 import java.lang.reflect.Modifier;
      9 import java.net.MalformedURLException;
     10 import java.net.URI;
     11 import java.net.URISyntaxException;
     12 import java.net.URL;
     13 import java.net.URLClassLoader;
     14 import java.net.URLConnection;
     15 import java.net.URLStreamHandler;
     16 import java.util.ArrayList;
     17 import java.util.Arrays;
     18 import java.util.Collections;
     19 import java.util.Enumeration;
     20 import java.util.HashMap;
     21 import java.util.HashSet;
     22 import java.util.Iterator;
     23 import java.util.List;
     24 import java.util.Map;
     25 import java.util.Set;
     26 import java.util.concurrent.ExecutionException;
     27 import java.util.concurrent.ExecutorService;
     28 import java.util.concurrent.Executors;
     29 import java.util.concurrent.Future;
     30 import java.util.concurrent.ThreadFactory;
     31 import org.objenesis.Objenesis;
     32 import org.objenesis.ObjenesisStd;
     33 import org.objenesis.instantiator.ObjectInstantiator;
     34 
     35 import static java.lang.String.format;
     36 import static java.util.Arrays.asList;
     37 
     38 public abstract class ClassLoaders {
     39     protected ClassLoader parent = currentClassLoader();
     40 
     41     protected ClassLoaders() {
     42     }
     43 
     44     public static IsolatedURLClassLoaderBuilder isolatedClassLoader() {
     45         return new IsolatedURLClassLoaderBuilder();
     46     }
     47 
     48     public static ExcludingURLClassLoaderBuilder excludingClassLoader() {
     49         return new ExcludingURLClassLoaderBuilder();
     50     }
     51 
     52     public static InMemoryClassLoaderBuilder inMemoryClassLoader() {
     53         return new InMemoryClassLoaderBuilder();
     54     }
     55 
     56     public static ReachableClassesFinder in(ClassLoader classLoader) {
     57         return new ReachableClassesFinder(classLoader);
     58     }
     59 
     60     public static ClassLoader jdkClassLoader() {
     61         return String.class.getClassLoader();
     62     }
     63 
     64     public static ClassLoader systemClassLoader() {
     65         return ClassLoader.getSystemClassLoader();
     66     }
     67 
     68     public static ClassLoader currentClassLoader() {
     69         return ClassLoaders.class.getClassLoader();
     70     }
     71 
     72     public abstract ClassLoader build();
     73 
     74     public static Class<?>[] coverageTool() {
     75         HashSet<Class<?>> classes = new HashSet<Class<?>>();
     76         classes.add(safeGetClass("net.sourceforge.cobertura.coveragedata.TouchCollector"));
     77         classes.add(safeGetClass("org.slf4j.LoggerFactory"));
     78 
     79         classes.remove(null);
     80         return classes.toArray(new Class<?>[classes.size()]);
     81     }
     82 
     83     private static Class<?> safeGetClass(String className) {
     84         try {
     85             return Class.forName(className);
     86         } catch (ClassNotFoundException e) {
     87             return null;
     88         }
     89     }
     90 
     91     public static ClassLoaderExecutor using(final ClassLoader classLoader) {
     92         return new ClassLoaderExecutor(classLoader);
     93     }
     94 
     95     public static class ClassLoaderExecutor {
     96         private ClassLoader classLoader;
     97 
     98         public ClassLoaderExecutor(ClassLoader classLoader) {
     99             this.classLoader = classLoader;
    100         }
    101 
    102         public void execute(final Runnable task) throws Exception {
    103             ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
    104                 @Override
    105                 public Thread newThread(Runnable r) {
    106                     Thread thread = Executors.defaultThreadFactory().newThread(r);
    107                     thread.setContextClassLoader(classLoader);
    108                     return thread;
    109                 }
    110             });
    111             try {
    112                 Future<?> taskFuture = executorService.submit(new Runnable() {
    113                     @Override
    114                     public void run() {
    115                         try {
    116                             reloadTaskInClassLoader(task).run();
    117                         } catch (Throwable throwable) {
    118                             throw new IllegalStateException(format("Given task could not be loaded properly in the given classloader '%s', error '%s",
    119                                                                    task,
    120                                                                    throwable.getMessage()),
    121                                                             throwable);
    122                         }
    123                     }
    124                 });
    125                 taskFuture.get();
    126                 executorService.shutdownNow();
    127             } catch (InterruptedException e) {
    128                 Thread.currentThread().interrupt();
    129             } catch (ExecutionException e) {
    130                 throw this.<Exception>unwrapAndThrows(e);
    131             }
    132         }
    133 
    134         @SuppressWarnings("unchecked")
    135         private <T extends Throwable> T unwrapAndThrows(ExecutionException ex) throws T {
    136             throw (T) ex.getCause();
    137         }
    138 
    139         Runnable reloadTaskInClassLoader(Runnable task) {
    140             try {
    141                 @SuppressWarnings("unchecked")
    142                 Class<Runnable> taskClassReloaded = (Class<Runnable>) classLoader.loadClass(task.getClass().getName());
    143 
    144                 Objenesis objenesis = new ObjenesisStd();
    145                 ObjectInstantiator<Runnable> thingyInstantiator = objenesis.getInstantiatorOf(taskClassReloaded);
    146                 Runnable reloaded = thingyInstantiator.newInstance();
    147 
    148                 // lenient shallow copy of class compatible fields
    149                 for (Field field : task.getClass().getDeclaredFields()) {
    150                     Field declaredField = taskClassReloaded.getDeclaredField(field.getName());
    151                     int modifiers = declaredField.getModifiers();
    152                     if(Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
    153                         // Skip static final fields (e.g. jacoco fields)
    154                         // otherwise IllegalAccessException (can be bypassed with Unsafe though)
    155                         // We may also miss coverage data.
    156                         continue;
    157                     }
    158                     if (declaredField.getType() == field.getType()) { // don't copy this
    159                         field.setAccessible(true);
    160                         declaredField.setAccessible(true);
    161                         declaredField.set(reloaded, field.get(task));
    162                     }
    163                 }
    164 
    165                 return reloaded;
    166             } catch (ClassNotFoundException e) {
    167                 throw new IllegalStateException(e);
    168             } catch (IllegalAccessException e) {
    169                 throw new IllegalStateException(e);
    170             } catch (NoSuchFieldException e) {
    171                 throw new IllegalStateException(e);
    172             }
    173         }
    174     }
    175 
    176     public static class IsolatedURLClassLoaderBuilder extends ClassLoaders {
    177         private final ArrayList<String> excludedPrefixes = new ArrayList<String>();
    178         private final ArrayList<String> privateCopyPrefixes = new ArrayList<String>();
    179         private final ArrayList<URL> codeSourceUrls = new ArrayList<URL>();
    180 
    181         public IsolatedURLClassLoaderBuilder withPrivateCopyOf(String... privatePrefixes) {
    182             privateCopyPrefixes.addAll(asList(privatePrefixes));
    183             return this;
    184         }
    185 
    186         public IsolatedURLClassLoaderBuilder withCodeSourceUrls(String... urls) {
    187             codeSourceUrls.addAll(pathsToURLs(urls));
    188             return this;
    189         }
    190 
    191         public IsolatedURLClassLoaderBuilder withCodeSourceUrlOf(Class<?>... classes) {
    192             for (Class<?> clazz : classes) {
    193                 codeSourceUrls.add(obtainCurrentClassPathOf(clazz.getName()));
    194             }
    195             return this;
    196         }
    197 
    198         public IsolatedURLClassLoaderBuilder withCurrentCodeSourceUrls() {
    199             codeSourceUrls.add(obtainCurrentClassPathOf(ClassLoaders.class.getName()));
    200             return this;
    201         }
    202 
    203         public IsolatedURLClassLoaderBuilder without(String... privatePrefixes) {
    204             excludedPrefixes.addAll(asList(privatePrefixes));
    205             return this;
    206         }
    207 
    208         public ClassLoader build() {
    209             return new LocalIsolatedURLClassLoader(
    210                     jdkClassLoader(),
    211                     codeSourceUrls.toArray(new URL[codeSourceUrls.size()]),
    212                     privateCopyPrefixes,
    213                     excludedPrefixes
    214             );
    215         }
    216     }
    217 
    218     static class LocalIsolatedURLClassLoader extends URLClassLoader {
    219         private final ArrayList<String> privateCopyPrefixes;
    220         private final ArrayList<String> excludedPrefixes;
    221 
    222         LocalIsolatedURLClassLoader(ClassLoader classLoader,
    223                                     URL[] urls,
    224                                     ArrayList<String> privateCopyPrefixes,
    225                                     ArrayList<String> excludedPrefixes) {
    226             super(urls, classLoader);
    227             this.privateCopyPrefixes = privateCopyPrefixes;
    228             this.excludedPrefixes = excludedPrefixes;
    229         }
    230 
    231         @Override
    232         public Class<?> findClass(String name) throws ClassNotFoundException {
    233             if (!classShouldBePrivate(name) || classShouldBeExcluded(name)) {
    234                 throw new ClassNotFoundException(format("Can only load classes with prefixes : %s, but not : %s",
    235                                                         privateCopyPrefixes,
    236                                                         excludedPrefixes));
    237             }
    238             try {
    239                 return super.findClass(name);
    240             } catch (ClassNotFoundException cnfe) {
    241                 throw new ClassNotFoundException(format("%s%n%s%n",
    242                                                         cnfe.getMessage(),
    243                                                         "    Did you forgot to add the code source url 'withCodeSourceUrlOf' / 'withCurrentCodeSourceUrls' ?"),
    244                                                  cnfe);
    245             }
    246         }
    247 
    248         private boolean classShouldBePrivate(String name) {
    249             for (String prefix : privateCopyPrefixes) {
    250                 if (name.startsWith(prefix)) return true;
    251             }
    252             return false;
    253         }
    254 
    255         private boolean classShouldBeExcluded(String name) {
    256             for (String prefix : excludedPrefixes) {
    257                 if (name.startsWith(prefix)) return true;
    258             }
    259             return false;
    260         }
    261     }
    262 
    263     public static class ExcludingURLClassLoaderBuilder extends ClassLoaders {
    264         private final ArrayList<String> excludedPrefixes = new ArrayList<String>();
    265         private final ArrayList<URL> codeSourceUrls = new ArrayList<URL>();
    266 
    267         public ExcludingURLClassLoaderBuilder without(String... privatePrefixes) {
    268             excludedPrefixes.addAll(asList(privatePrefixes));
    269             return this;
    270         }
    271 
    272         public ExcludingURLClassLoaderBuilder withCodeSourceUrls(String... urls) {
    273             codeSourceUrls.addAll(pathsToURLs(urls));
    274             return this;
    275         }
    276 
    277         public ExcludingURLClassLoaderBuilder withCodeSourceUrlOf(Class<?>... classes) {
    278             for (Class<?> clazz : classes) {
    279                 codeSourceUrls.add(obtainCurrentClassPathOf(clazz.getName()));
    280             }
    281             return this;
    282         }
    283 
    284         public ExcludingURLClassLoaderBuilder withCurrentCodeSourceUrls() {
    285             codeSourceUrls.add(obtainCurrentClassPathOf(ClassLoaders.class.getName()));
    286             return this;
    287         }
    288 
    289         public ClassLoader build() {
    290             return new LocalExcludingURLClassLoader(
    291                     jdkClassLoader(),
    292                     codeSourceUrls.toArray(new URL[codeSourceUrls.size()]),
    293                     excludedPrefixes
    294             );
    295         }
    296     }
    297 
    298     static class LocalExcludingURLClassLoader extends URLClassLoader {
    299         private final ArrayList<String> excludedPrefixes;
    300 
    301         LocalExcludingURLClassLoader(ClassLoader classLoader,
    302                                      URL[] urls,
    303                                      ArrayList<String> excludedPrefixes) {
    304             super(urls, classLoader);
    305             this.excludedPrefixes = excludedPrefixes;
    306         }
    307 
    308         @Override
    309         public Class<?> findClass(String name) throws ClassNotFoundException {
    310             if (classShouldBeExcluded(name))
    311                 throw new ClassNotFoundException("classes with prefix : " + excludedPrefixes + " are excluded");
    312             return super.findClass(name);
    313         }
    314 
    315         private boolean classShouldBeExcluded(String name) {
    316             for (String prefix : excludedPrefixes) {
    317                 if (name.startsWith(prefix)) return true;
    318             }
    319             return false;
    320         }
    321     }
    322 
    323     public static class InMemoryClassLoaderBuilder extends ClassLoaders {
    324         private Map<String, byte[]> inMemoryClassObjects = new HashMap<String, byte[]>();
    325 
    326         public InMemoryClassLoaderBuilder withParent(ClassLoader parent) {
    327             this.parent = parent;
    328             return this;
    329         }
    330 
    331         public InMemoryClassLoaderBuilder withClassDefinition(String name, byte[] classDefinition) {
    332             inMemoryClassObjects.put(name, classDefinition);
    333             return this;
    334         }
    335 
    336         public ClassLoader build() {
    337             return new InMemoryClassLoader(parent, inMemoryClassObjects);
    338         }
    339     }
    340 
    341     static class InMemoryClassLoader extends ClassLoader {
    342         public static final String SCHEME = "mem";
    343         private Map<String, byte[]> inMemoryClassObjects = new HashMap<String, byte[]>();
    344 
    345         public InMemoryClassLoader(ClassLoader parent, Map<String, byte[]> inMemoryClassObjects) {
    346             super(parent);
    347             this.inMemoryClassObjects = inMemoryClassObjects;
    348         }
    349 
    350         protected Class<?> findClass(String name) throws ClassNotFoundException {
    351             byte[] classDefinition = inMemoryClassObjects.get(name);
    352             if (classDefinition != null) {
    353                 return defineClass(name, classDefinition, 0, classDefinition.length);
    354             }
    355             throw new ClassNotFoundException(name);
    356         }
    357 
    358         @Override
    359         public Enumeration<URL> getResources(String ignored) throws IOException {
    360             return inMemoryOnly();
    361         }
    362 
    363         private Enumeration<URL> inMemoryOnly() {
    364             final Set<String> names = inMemoryClassObjects.keySet();
    365             return new Enumeration<URL>() {
    366                 private final MemHandler memHandler = new MemHandler(InMemoryClassLoader.this);
    367                 private final Iterator<String> it = names.iterator();
    368 
    369                 public boolean hasMoreElements() {
    370                     return it.hasNext();
    371                 }
    372 
    373                 public URL nextElement() {
    374                     try {
    375                         return new URL(null, SCHEME + ":" + it.next(), memHandler);
    376                     } catch (MalformedURLException rethrown) {
    377                         throw new IllegalStateException(rethrown);
    378                     }
    379                 }
    380             };
    381         }
    382     }
    383 
    384     public static class MemHandler extends URLStreamHandler {
    385         private InMemoryClassLoader inMemoryClassLoader;
    386 
    387         public MemHandler(InMemoryClassLoader inMemoryClassLoader) {
    388             this.inMemoryClassLoader = inMemoryClassLoader;
    389         }
    390 
    391         @Override
    392         protected URLConnection openConnection(URL url) throws IOException {
    393             return new MemURLConnection(url, inMemoryClassLoader);
    394         }
    395 
    396         private static class MemURLConnection extends URLConnection {
    397             private final InMemoryClassLoader inMemoryClassLoader;
    398             private String qualifiedName;
    399 
    400             public MemURLConnection(URL url, InMemoryClassLoader inMemoryClassLoader) {
    401                 super(url);
    402                 this.inMemoryClassLoader = inMemoryClassLoader;
    403                 qualifiedName = url.getPath();
    404             }
    405 
    406             @Override
    407             public void connect() throws IOException {
    408             }
    409 
    410             @Override
    411             public InputStream getInputStream() throws IOException {
    412                 return new ByteArrayInputStream(inMemoryClassLoader.inMemoryClassObjects.get(qualifiedName));
    413             }
    414         }
    415     }
    416 
    417     URL obtainCurrentClassPathOf(String className) {
    418         String path = className.replace('.', '/') + ".class";
    419         String url = ClassLoaders.class.getClassLoader().getResource(path).toExternalForm();
    420 
    421         try {
    422             return new URL(url.substring(0, url.length() - path.length()));
    423         } catch (MalformedURLException e) {
    424             throw new RuntimeException("Classloader couldn't obtain a proper classpath URL", e);
    425         }
    426     }
    427 
    428     List<URL> pathsToURLs(String... codeSourceUrls) {
    429         return pathsToURLs(Arrays.asList(codeSourceUrls));
    430     }
    431 
    432     private List<URL> pathsToURLs(List<String> codeSourceUrls) {
    433         ArrayList<URL> urls = new ArrayList<URL>(codeSourceUrls.size());
    434         for (String codeSourceUrl : codeSourceUrls) {
    435             URL url = pathToUrl(codeSourceUrl);
    436             urls.add(url);
    437         }
    438         return urls;
    439     }
    440 
    441     private URL pathToUrl(String path) {
    442         try {
    443             return new File(path).getAbsoluteFile().toURI().toURL();
    444         } catch (MalformedURLException e) {
    445             throw new IllegalArgumentException("Path is malformed", e);
    446         }
    447     }
    448 
    449     public static class ReachableClassesFinder {
    450         private ClassLoader classLoader;
    451         private Set<String> qualifiedNameSubstring = new HashSet<String>();
    452 
    453         ReachableClassesFinder(ClassLoader classLoader) {
    454             this.classLoader = classLoader;
    455         }
    456 
    457         public ReachableClassesFinder omit(String... qualifiedNameSubstring) {
    458             this.qualifiedNameSubstring.addAll(Arrays.asList(qualifiedNameSubstring));
    459             return this;
    460         }
    461 
    462         public Set<String> listOwnedClasses() throws IOException, URISyntaxException {
    463             Enumeration<URL> roots = classLoader.getResources("");
    464 
    465             Set<String> classes = new HashSet<String>();
    466             while (roots.hasMoreElements()) {
    467                 URI uri = roots.nextElement().toURI();
    468 
    469                 if (uri.getScheme().equalsIgnoreCase("file")) {
    470                     addFromFileBasedClassLoader(classes, uri);
    471                 } else if (uri.getScheme().equalsIgnoreCase(InMemoryClassLoader.SCHEME)) {
    472                     addFromInMemoryBasedClassLoader(classes, uri);
    473                 } else {
    474                     throw new IllegalArgumentException(format("Given ClassLoader '%s' don't have reachable by File or vi ClassLoaders.inMemory", classLoader));
    475                 }
    476             }
    477             return classes;
    478         }
    479 
    480         private void addFromFileBasedClassLoader(Set<String> classes, URI uri) {
    481             File root = new File(uri);
    482             classes.addAll(findClassQualifiedNames(root, root, qualifiedNameSubstring));
    483         }
    484 
    485         private void addFromInMemoryBasedClassLoader(Set<String> classes, URI uri) {
    486             String qualifiedName = uri.getSchemeSpecificPart();
    487             if (excludes(qualifiedName, qualifiedNameSubstring)) {
    488                 classes.add(qualifiedName);
    489             }
    490         }
    491 
    492 
    493         private Set<String> findClassQualifiedNames(File root, File file, Set<String> packageFilters) {
    494             if (file.isDirectory()) {
    495                 File[] files = file.listFiles();
    496                 Set<String> classes = new HashSet<String>();
    497                 for (File children : files) {
    498                     classes.addAll(findClassQualifiedNames(root, children, packageFilters));
    499                 }
    500                 return classes;
    501             } else {
    502                 if (file.getName().endsWith(".class")) {
    503                     String qualifiedName = classNameFor(root, file);
    504                     if (excludes(qualifiedName, packageFilters)) {
    505                         return Collections.singleton(qualifiedName);
    506                     }
    507                 }
    508             }
    509             return Collections.emptySet();
    510         }
    511 
    512         private boolean excludes(String qualifiedName, Set<String> packageFilters) {
    513             for (String filter : packageFilters) {
    514                 if (qualifiedName.contains(filter)) return false;
    515             }
    516             return true;
    517         }
    518 
    519         private String classNameFor(File root, File file) {
    520             String temp = file.getAbsolutePath().substring(root.getAbsolutePath().length() + 1).
    521                     replace(File.separatorChar, '.');
    522             return temp.subSequence(0, temp.indexOf(".class")).toString();
    523         }
    524 
    525     }
    526 }
    527