/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.state; import java.lang.instrument.*; import java.security.*; import java.util.*; import javax.annotation.*; import mockit.internal.startup.*; /** * Holds a map of internal class names to the corresponding class files (bytecode arrays), for the classes * that have already been loaded during the test run. * These classfiles are not necessarily the same as those stored in the corresponding ".class" files * available from the runtime classpath. * If any third-party {@link ClassFileTransformer}s are active, those original classfiles may have been modified before * being loaded by the JVM. * JMockit installs a {@code ClassFileTransformer} of its own which saves all potentially modified classfiles * here. * <p/> * This bytecode cache allows classes to be mocked and un-mocked correctly, even in the presence of other * bytecode modification agents such as the AspectJ load-time weaver. */ public final class CachedClassfiles implements ClassFileTransformer { @Nonnull public static final CachedClassfiles INSTANCE = new CachedClassfiles(); @Nonnull private final Map<ClassLoader, Map<String, byte[]>> classLoadersAndClassfiles; @Nullable private Class<?> classBeingCached; private CachedClassfiles() { classLoadersAndClassfiles = new WeakHashMap<ClassLoader, Map<String, byte[]>>(2); } @Nullable @Override public byte[] transform( @Nullable ClassLoader loader, String classDesc, @Nullable Class<?> classBeingRedefinedOrRetransformed, @Nullable ProtectionDomain protectionDomain, @Nonnull byte[] classfileBuffer) { if (classDesc != null) { // can be null for Java 8 lambdas if (classBeingRedefinedOrRetransformed != null && classBeingRedefinedOrRetransformed == classBeingCached) { addClassfile(loader, classDesc, classfileBuffer); classBeingCached = null; } } return null; } private void addClassfile(@Nullable ClassLoader loader, @Nonnull String classDesc, @Nonnull byte[] classfile) { Map<String, byte[]> classfiles = getClassfiles(loader); classfiles.put(classDesc, classfile); } @Nonnull private Map<String, byte[]> getClassfiles(@Nullable ClassLoader loader) { Map<String, byte[]> classfiles = classLoadersAndClassfiles.get(loader); if (classfiles == null) { classfiles = new HashMap<String, byte[]>(100); classLoadersAndClassfiles.put(loader, classfiles); } return classfiles; } @Nullable private byte[] findClassfile(@Nonnull Class<?> aClass) { String className = aClass.getName(); // Discards an invalid numerical suffix from a synthetic Java 8 class, if detected. int p = className.indexOf('/'); if (p > 0) className = className.substring(0, p); Map<String, byte[]> classfiles = getClassfiles(aClass.getClassLoader()); return classfiles.get(className.replace('.', '/')); } @Nullable public static synchronized byte[] getClassfile(@Nonnull String classDesc) { return INSTANCE.findClassfile(classDesc); } @Nullable private byte[] findClassfile(@Nonnull String classDesc) { byte[] classfile = null; for (Map<String, byte[]> classfiles : classLoadersAndClassfiles.values()) { classfile = classfiles.get(classDesc); if (classfile != null) { return classfile; } } Class<?> desiredClass = Startup.getClassIfLoaded(classDesc); if (desiredClass != null) { classBeingCached = desiredClass; Startup.retransformClass(desiredClass); ClassLoader classLoader = desiredClass.getClassLoader(); classfile = INSTANCE.findClassfile(classLoader, classDesc); } return classfile; } @Nullable private synchronized byte[] findClassfile(@Nullable ClassLoader loader, @Nonnull String classDesc) { Map<String, byte[]> classfiles = getClassfiles(loader); return classfiles.get(classDesc); } @Nullable public static synchronized byte[] getClassfile(@Nonnull Class<?> aClass) { byte[] cached = INSTANCE.findClassfile(aClass); if (cached != null) return cached; INSTANCE.classBeingCached = aClass; Startup.retransformClass(aClass); return INSTANCE.findClassfile(aClass); } @Nullable public static byte[] getClassfile(@Nullable ClassLoader loader, @Nonnull String internalClassName) { return INSTANCE.findClassfile(loader, internalClassName); } public static void addClassfile(@Nonnull Class<?> aClass, @Nonnull byte[] classfile) { INSTANCE.addClassfile(aClass.getClassLoader(), aClass.getName().replace('.', '/'), classfile); } }