/* * Copyright 2013 Christopher Pheby * * 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.jadira.reflection.access.classloader; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * A ClassLoader which can be used to load classes from arbitrary byte arrays. * Jadira uses this to load classes generated using ASM. */ public class AccessClassLoader extends ClassLoader { private static final Method DEFINE_METHOD; static { Method defineMethod = null; try { defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class }); defineMethod.setAccessible(true); } catch (NoSuchMethodException e) { } catch (SecurityException e) { } DEFINE_METHOD = defineMethod; } private static final ConcurrentHashMap<ClassLoader, AccessClassLoader> ASM_CLASS_LOADERS = new ConcurrentHashMap<ClassLoader, AccessClassLoader>(); private static final Map<String, byte[]> registeredClasses = new HashMap<String, byte[]>(); /** * Creates a new instance using a suitable ClassLoader for the specified class * @param typeToBeExtended The class to use to obtain a ClassLoader * @return A new instance, or an existing instance if one already exists. */ public static final AccessClassLoader get(Class<?> typeToBeExtended) { ClassLoader loader = typeToBeExtended.getClassLoader(); return get(loader == null ? ClassLoader.getSystemClassLoader() : loader); } /** * Creates an AccessClassLoader for the given parent * @param parent The parent ClassLoader for this instance * @return A new instance, or an existing instance if one already exists. */ public synchronized static final AccessClassLoader get(ClassLoader parent) { AccessClassLoader loader = (AccessClassLoader) ASM_CLASS_LOADERS.get(parent); if (loader == null) { loader = new AccessClassLoader(parent); ASM_CLASS_LOADERS.put(parent, loader); } return loader; } private AccessClassLoader(ClassLoader parentClassLoader) { super(parentClassLoader); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null) { try { loadedClass = findClass(name); } catch (ClassNotFoundException e) { // Ignore } if (loadedClass == null) { loadedClass = super.loadClass(name); } } return loadedClass; } /** * Registers a class by its name * @param name The name of the class to be registered * @param bytes An array of bytes containing the class */ public void registerClass(String name, byte[] bytes) { if (registeredClasses.containsKey(name)) { throw new IllegalStateException("Attempted to register a class that has been registered already: " + name); } registeredClasses.put(name, bytes); } @Override public Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = registeredClasses.get(name); if (bytes != null) { registeredClasses.remove(name); try { return (Class<?>) DEFINE_METHOD.invoke(getParent(), new Object[] { name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length) }); } catch (Exception ignored) { } return defineClass(name, bytes, 0, bytes.length); } throw new ClassNotFoundException("Cannot find class: " + name); } }