/* * JCarder -- cards Java programs to keep threads disentangled * * Copyright (C) 2006-2007 Enea AB * Copyright (C) 2007 Ulrik Svensson * Copyright (C) 2007 Joel Rosdahl * * This program is made available under the GNU GPL version 2, with a special * exception for linking with JUnit. See the accompanying file LICENSE.txt for * details. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ package com.enea.jcarder.agent.instrument; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * This class loader is able to transform classes while they are loaded. * It uses the classpath from its parent class loader. * * The class loader that loaded the TransformClassLoader will be used * as parrent class loader. * * This TransformClassLoader is not able to transform classes that begin * with java.*. */ public final class TransformClassLoader extends ClassLoader { private final ClassFileTransformer mTransformer; private final ClassLoader mParentClassLoader; private final Pattern mClassNamePattern; private final Set<String> mTansformedClasses = new HashSet<String>(); /** * Create a TransformClassLoader that only transforms classes that are * explicitly loaded with the transform(Class) method. All other * requests are forwarded to the parent class loader. * */ public TransformClassLoader(final ClassFileTransformer transformer) { this(transformer, null); } /** * Create a TransformClassLoader that transforms all classes with names * that match the given classNamePattern regexp. All other requests are * forwarded to the parent class loader. */ public TransformClassLoader(final ClassFileTransformer transformer, final Pattern classNamePattern) { mTransformer = transformer; mParentClassLoader = TransformClassLoader.class.getClassLoader(); mClassNamePattern = classNamePattern; } /** * This method transforms a class even if its name does not match the * classNamePattern regexp that this TransformClassLoader may have been * configured * with. */ public Class<?> transform(Class clazz) throws ClassNotFoundException, IllegalClassFormatException, ClassNotTransformedException { byte[] classBuffer = getClassBytes(clazz); return createTransformedClass(clazz.getName(), classBuffer); } /** * Load a new non-instrumented class with this class loader. */ public Class<?> loadNew(Class clazz) throws ClassNotFoundException { byte[] classBuffer = getClassBytes(clazz); return defineClass(clazz.getName(), classBuffer, 0, classBuffer.length); } protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(className); if (c == null) { if (isClassNameToBeTransformed(className)) { c = findClass(className); } else { c = mParentClassLoader.loadClass(className); } } if (resolve) { resolveClass(c); } return c; } private boolean isClassNameToBeTransformed(String className) { return mClassNamePattern != null && mClassNamePattern.matcher(className).matches(); } protected synchronized Class<?> findClass(String className) throws ClassNotFoundException { byte[] classBuffer = getClassBytes(className, mParentClassLoader); try { return createTransformedClass(className, classBuffer); } catch (IllegalClassFormatException e) { throw new ClassNotFoundException(e.getMessage(), e); } catch (ClassNotTransformedException e) { throw new ClassNotFoundException(e.getMessage(), e); } } private Class<?> createTransformedClass(String className, byte[] classBuffer) throws ClassNotFoundException, IllegalClassFormatException, ClassNotTransformedException { byte[] transformedClassBuffer = mTransformer.transform(this, className, null, null, classBuffer); if (transformedClassBuffer == null) { throw new ClassNotTransformedException("Class not transformed: " + className); } mTansformedClasses.add(className); return defineClass(className, transformedClassBuffer, 0, transformedClassBuffer.length); } public boolean hasBeenTransformed(String className) { return mTansformedClasses.contains(className); } public static String resourceNameForClass(String className) { return className.replace(".", "/") + ".class"; } /** * * @param className * @param classLoader null if the system classloader should be used. * @return * @throws ClassNotFoundException */ public static byte[] getClassBytes(String className, ClassLoader classLoader) throws ClassNotFoundException { final String resourceName = resourceNameForClass(className); final InputStream is; if (classLoader == null) { is = ClassLoader.getSystemResourceAsStream(resourceName); } else { is = classLoader.getResourceAsStream(resourceName); } if (is == null) { throw new ClassNotFoundException("Resource not found: " + resourceName); } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final byte[] readBuffer = new byte[1024 * 4]; int readBytes; try { while ((readBytes = is.read(readBuffer)) != -1) { baos.write(readBuffer, 0, readBytes); } } catch (IOException e) { throw new ClassNotFoundException("Failure while reading class contents", e); } return baos.toByteArray(); } public static byte[] getClassBytes(Class clazz) throws ClassNotFoundException { return getClassBytes(clazz.getName(), clazz.getClassLoader()); } }