/* * Copyright 2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.powermock.core.classloader; import javassist.Loader; import org.powermock.core.WildcardMatcher; import org.powermock.reflect.Whitebox; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.util.Enumeration; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Defers classloading of system classes to a delegate. * * @author Johan Haleby * @author Jan Kronquist */ public abstract class DeferSupportingClassLoader extends Loader { private final ConcurrentMap<String, SoftReference<Class<?>>> classes; String[] deferPackages; ClassLoader deferTo; public void addIgnorePackage(String... packagesToIgnore) { if (packagesToIgnore != null && packagesToIgnore.length > 0) { final int previousLength = deferPackages.length; String[] newDeferPackages = new String[previousLength + packagesToIgnore.length]; System.arraycopy(deferPackages, 0, newDeferPackages, 0, previousLength); System.arraycopy(packagesToIgnore, 0, newDeferPackages, previousLength, packagesToIgnore.length); deferPackages = newDeferPackages; } } DeferSupportingClassLoader(ClassLoader classloader, String deferPackages[]) { if (classloader == null) { deferTo = ClassLoader.getSystemClassLoader(); } else { deferTo = classloader; } classes = new ConcurrentHashMap<String, SoftReference<Class<?>>>(); this.deferPackages = deferPackages; } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass1(name); if (clazz == null){ clazz = loadClass1(name, resolve); } return clazz; } private Class<?> loadClass1(String name, boolean resolve) throws ClassNotFoundException { Class<?> clazz; if (shouldDefer(deferPackages, name)) { clazz = deferTo.loadClass(name); } else { clazz = loadModifiedClass(name); } if (resolve) { resolveClass(clazz); } classes.put(name, new SoftReference<Class<?>>(clazz)); return clazz; } private Class<?> findLoadedClass1(String name) {SoftReference<Class<?>> reference = classes.get(name); Class<?> clazz = null; if (reference != null) { clazz = reference.get(); } if (clazz == null) { clazz = findLoadedClass(name); } return clazz; } boolean shouldDefer(String[] packages, String name) { for (String packageToCheck : packages) { if (deferConditionMatches(name, packageToCheck)) { return true; } } return false; } private boolean deferConditionMatches(String name, String packageName) { final boolean wildcardMatch = WildcardMatcher.matches(name, packageName); return wildcardMatch && !(shouldLoadUnmodifiedClass(name) || shouldModifyClass(name)); } private boolean shouldIgnore(Iterable<String> packages, String name) { for (String ignore : packages) { if (WildcardMatcher.matches(ignore, name)) { return true; } } return false; } boolean shouldIgnore(String[] packages, String name) { for (String ignore : packages) { if (WildcardMatcher.matches(name, ignore)) { return true; } } return false; } /** * Finds the resource with the specified name on the search path. * * @param name the name of the resource * @return a <code>URL</code> for the resource, or <code>null</code> if the * resource could not be found. */ @Override protected URL findResource(String name) { try { return Whitebox.invokeMethod(deferTo, "findResource", name); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected Enumeration<URL> findResources(String name) throws IOException { try { return Whitebox.invokeMethod(deferTo, "findResources", name); } catch (Exception e) { throw new RuntimeException(e); } } @Override public URL getResource(String s) { return deferTo.getResource(s); } @Override public InputStream getResourceAsStream(String s) { return deferTo.getResourceAsStream(s); } @Override public Enumeration<URL> getResources(String name) throws IOException { // If deferTo is already the parent, then we'd end up returning two copies of each resource... if (deferTo.equals(getParent())) return deferTo.getResources(name); else return super.getResources(name); } protected boolean shouldModify(Iterable<String> packages, String name) { return !shouldIgnore(packages, name); } protected abstract Class<?> loadModifiedClass(String s) throws ClassFormatError, ClassNotFoundException; protected abstract boolean shouldModifyClass(String s); protected abstract boolean shouldLoadUnmodifiedClass(String className); /** * Register a class to the cache of this classloader */ public void cache(Class<?> cls) { if (cls != null) { classes.put(cls.getName(), new SoftReference<Class<?>>(cls)); } } }