/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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.civilian.controller.classloader; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import org.civilian.internal.Logs; import org.civilian.util.IoUtil; /** * A ClassLoader with a non-delegating class-loading strategy. * When requested to find a class or resource, this ClassLoader will try * to first load the class itself before delegating to the parent class loader. */ public class NonDelegatingClassLoader extends ClassLoader { private static final Method FINDELOADEDCLASS_METHOD; static { try { FINDELOADEDCLASS_METHOD = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); FINDELOADEDCLASS_METHOD.setAccessible(true); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public NonDelegatingClassLoader(ClassLoader parent, ClassList includes, ClassList excludes) { super(parent); includes_ = includes; excludes_ = excludes; } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c; // check if loaded by a parent or by ourself c = findParentLoadedClass(name); if (c != null) return c; c = findLoadedClass(name); if (c != null) return c; // we see a new class if (Logs.CLASSLOADER.isTraceEnabled()) Logs.CLASSLOADER.trace("requested: " + name); if (canLoad(name)) { c = defineFileClass(name); if (c != null) { if (Logs.CLASSLOADER.isTraceEnabled()) Logs.CLASSLOADER.trace("loaded: " + name); if (resolve) resolveClass(c); return c; } } if (Logs.CLASSLOADER.isTraceEnabled()) Logs.CLASSLOADER.trace("delegated: " + name); return super.loadClass(name, resolve); } protected Class<?> findParentLoadedClass(String name) { try { return (Class<?>)FINDELOADEDCLASS_METHOD.invoke(getParent(), name); } catch (Exception e) { Logs.CLASSLOADER.error("error when calling 'findLoadedClass'", e); return null; } } /** * We are only interested in loading classes which are files * on the disk. */ protected Class<?> defineFileClass(String name) { String resource = name.replace('.', '/') + ".class"; URL url = getParent().getResource(resource); if ((url != null) && "file".equals(url.getProtocol())) { try { try(InputStream in = url.openStream()) { byte[] data = IoUtil.readBytes(in); return defineClass(name, data, 0, data.length); } } catch(IOException e) { Logs.CLASSLOADER.error("error when loading class from '" + url + "'", e); } } return null; } private boolean canLoad(String name) { // if the class is an inner class, we may not load it if // the outer class was loaded by the parent class loader // (even if the inner class was not yet loaded) int p = name.indexOf('$'); if (p >= 0) { String outerClass = name.substring(0, p); Class<?> c = findLoadedClass(outerClass); if ((c != null) && (c.getClassLoader() != this)) return false; } return isIncluded(name); } public boolean isIncluded(String name) { if (excludes_.contains(name)) return false; else if (includes_.contains(name)) return true; else return false; } private ClassList excludes_ = new ClassList(); private ClassList includes_ = new ClassList(); }