aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--loadtime/src/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java42
-rw-r--r--weaver/src/org/aspectj/weaver/tools/WeavingAdaptor.java89
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CacheBacking.java58
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CacheFactory.java25
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CacheKeyResolver.java88
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CacheStatistics.java106
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CachedClassEntry.java57
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/CachedClassReference.java57
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheFactory.java26
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolver.java120
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/DefaultFileCacheBacking.java309
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/GeneratedCachedClassHandler.java38
-rw-r--r--weaver/src/org/aspectj/weaver/tools/cache/WeavedClassCache.java327
-rw-r--r--weaver/testsrc/org/aspectj/weaver/BcweaverModuleTests.java2
-rw-r--r--weaver/testsrc/org/aspectj/weaver/tools/cache/CacheTests.java28
-rw-r--r--weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolverTest.java92
-rw-r--r--weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultFileCacheBackingTest.java171
-rw-r--r--weaver/testsrc/org/aspectj/weaver/tools/cache/WeavedClassCacheTest.java168
18 files changed, 1768 insertions, 35 deletions
diff --git a/loadtime/src/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java b/loadtime/src/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java
index 868c94c48..0615ff489 100644
--- a/loadtime/src/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java
+++ b/loadtime/src/org/aspectj/weaver/loadtime/ClassLoaderWeavingAdaptor.java
@@ -9,26 +9,17 @@
* Contributors:
* Alexandre Vasseur initial implementation
* David Knibb weaving context enhancments
+ * John Kew (vmware) caching hook
*******************************************************************************/
package org.aspectj.weaver.loadtime;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
+import java.util.*;
import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.Constants;
@@ -47,10 +38,8 @@ import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.ltw.LTWWorld;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
-import org.aspectj.weaver.tools.GeneratedClassHandler;
-import org.aspectj.weaver.tools.Trace;
-import org.aspectj.weaver.tools.TraceFactory;
-import org.aspectj.weaver.tools.WeavingAdaptor;
+import org.aspectj.weaver.tools.*;
+import org.aspectj.weaver.tools.cache.WeavedClassCache;
/**
* @author Alexandre Vasseur
@@ -195,6 +184,9 @@ public class ClassLoaderWeavingAdaptor extends WeavingAdaptor {
bcelWorld = null;
weaver = null;
}
+ if (WeavedClassCache.isEnabled()) {
+ initializeCache(classLoader, getAspectClassNames(definitions), generatedClassHandler, getMessageHandler());
+ }
initialized = true;
if (trace.isTraceEnabled()) {
@@ -203,9 +195,25 @@ public class ClassLoaderWeavingAdaptor extends WeavingAdaptor {
}
/**
+ * Get the list of all aspects from the defintion list
+ * @param definitions
+ * @return
+ */
+ List<String> getAspectClassNames(List<Definition> definitions) {
+ List<String> aspects = new LinkedList<String>();
+ for (Iterator<Definition> it = definitions.iterator(); it.hasNext(); ) {
+ Definition def = it.next();
+ List<String> defAspects = def.getAspectClassNames();
+ if (defAspects != null) {
+ aspects.addAll(defAspects);
+ }
+ }
+ return aspects;
+ }
+
+ /**
* Load and cache the aop.xml/properties according to the classloader visibility rules
*
- * @param weaver
* @param loader
*/
List<Definition> parseDefinitions(final ClassLoader loader) {
diff --git a/weaver/src/org/aspectj/weaver/tools/WeavingAdaptor.java b/weaver/src/org/aspectj/weaver/tools/WeavingAdaptor.java
index 7ac87ac9d..f23794647 100644
--- a/weaver/src/org/aspectj/weaver/tools/WeavingAdaptor.java
+++ b/weaver/src/org/aspectj/weaver/tools/WeavingAdaptor.java
@@ -7,7 +7,7 @@
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
- * Matthew Webster, Adrian Colyer,
+ * Matthew Webster, Adrian Colyer, John Kew (caching)
* Martin Lippert initial implementation
* ******************************************************************/
@@ -20,16 +20,7 @@ import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
+import java.util.*;
import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.IMessage;
@@ -53,6 +44,9 @@ import org.aspectj.weaver.bcel.BcelObjectType;
import org.aspectj.weaver.bcel.BcelWeaver;
import org.aspectj.weaver.bcel.BcelWorld;
import org.aspectj.weaver.bcel.UnwovenClassFile;
+import org.aspectj.weaver.tools.cache.CachedClassEntry;
+import org.aspectj.weaver.tools.cache.CachedClassReference;
+import org.aspectj.weaver.tools.cache.WeavedClassCache;
// OPTIMIZE add guards for all the debug/info/etc
/**
@@ -88,6 +82,7 @@ public class WeavingAdaptor implements IMessageContext {
protected ProtectionDomain activeProtectionDomain;
private boolean haveWarnedOnJavax = false;
+ protected WeavedClassCache cache;
private int weavingSpecialTypes = 0;
private static final int INITIALIZED = 0x1;
@@ -102,18 +97,18 @@ public class WeavingAdaptor implements IMessageContext {
/**
* Construct a WeavingAdaptor with a reference to a weaving class loader. The adaptor will automatically search the class loader
* hierarchy to resolve classes. The adaptor will also search the hierarchy for WeavingClassLoader instances to determine the
- * set of aspects to be used ofr weaving.
+ * set of aspects to be used for weaving.
*
* @param loader instance of <code>ClassLoader</code>
*/
public WeavingAdaptor(WeavingClassLoader loader) {
// System.err.println("? WeavingAdaptor.<init>(" + loader +"," + aspectURLs.length + ")");
generatedClassHandler = loader;
- init(getFullClassPath((ClassLoader) loader), getFullAspectPath((ClassLoader) loader/* ,aspectURLs */));
+ init((ClassLoader)loader, getFullClassPath((ClassLoader) loader), getFullAspectPath((ClassLoader) loader/* ,aspectURLs */));
}
/**
- * Construct a WeavingAdator with a reference to a <code>GeneratedClassHandler</code>, a full search path for resolving classes
+ * Construct a WeavingAdaptor with a reference to a <code>GeneratedClassHandler</code>, a full search path for resolving classes
* and a complete set of aspects. The search path must include classes loaded by the class loader constructing the
* WeavingAdaptor and all its parents in the hierarchy.
*
@@ -124,10 +119,10 @@ public class WeavingAdaptor implements IMessageContext {
public WeavingAdaptor(GeneratedClassHandler handler, URL[] classURLs, URL[] aspectURLs) {
// System.err.println("? WeavingAdaptor.<init>()");
generatedClassHandler = handler;
- init(FileUtil.makeClasspath(classURLs), FileUtil.makeClasspath(aspectURLs));
+ init(null, FileUtil.makeClasspath(classURLs), FileUtil.makeClasspath(aspectURLs));
}
- private List<String> getFullClassPath(ClassLoader loader) {
+ protected List<String> getFullClassPath(ClassLoader loader) {
List<String> list = new LinkedList<String>();
for (; loader != null; loader = loader.getParent()) {
if (loader instanceof URLClassLoader) {
@@ -163,7 +158,13 @@ public class WeavingAdaptor implements IMessageContext {
}
}
- private void init(List<String> classPath, List<String> aspectPath) {
+ /**
+ * Initialize the WeavingAdapter
+ * @param loader ClassLoader used by this adapter; which can be null
+ * @param classPath classpath of this adapter
+ * @param aspectPath list of aspect paths
+ */
+ private void init(ClassLoader loader, List<String> classPath, List<String> aspectPath) {
abortOnError = true;
createMessageHandler();
@@ -179,10 +180,30 @@ public class WeavingAdaptor implements IMessageContext {
weaver = new BcelWeaver(bcelWorld);
registerAspectLibraries(aspectPath);
-
+ initializeCache(loader, aspectPath, null, getMessageHandler());
enabled = true;
}
+ /**
+ * If the cache is enabled, initialize it and swap out the existing classhandler
+ * for the caching one -
+ *
+ * @param loader classloader for this adapter, may be null
+ * @param aspects List of strings representing aspects managed by the adapter; these could be urls or classnames
+ * @param existingClassHandler current class handler
+ * @param myMessageHandler current message handler
+ */
+ protected void initializeCache(ClassLoader loader, List<String> aspects, GeneratedClassHandler existingClassHandler, IMessageHandler myMessageHandler) {
+ if (WeavedClassCache.isEnabled()) {
+ cache = WeavedClassCache.createCache(loader, aspects, existingClassHandler, myMessageHandler);
+ // Wrap the existing class handler so that any generated classes are also cached
+ if (cache != null) {
+ this.generatedClassHandler = cache.getCachingClassHandler();
+ }
+ }
+ }
+
+
protected void createMessageHandler() {
messageHolder = new WeavingAdaptorMessageHolder(new PrintWriter(System.err));
messageHandler = messageHolder;
@@ -312,6 +333,23 @@ public class WeavingAdaptor implements IMessageContext {
name = name.replace('/', '.');
if (couldWeave(name, bytes)) {
if (accept(name, bytes)) {
+
+ // Determine if we have the weaved class cached
+ CachedClassReference cacheKey = null;
+ byte[] original_bytes = bytes;
+ if (cache != null && !mustWeave) {
+ cacheKey = cache.createCacheKey(name, bytes);
+ CachedClassEntry entry = cache.get(cacheKey);
+ if (entry != null) {
+ // If the entry has been explicitly ignored
+ // return the original bytes
+ if (entry.isIgnored()) {
+ return bytes;
+ }
+ return entry.getBytes();
+ }
+ }
+
// TODO @AspectJ problem
// Annotation style aspects need to be included regardless in order to get
// a valid aspectOf()/hasAspect() generated in them. However - if they are excluded
@@ -334,6 +372,21 @@ public class WeavingAdaptor implements IMessageContext {
// debug("weaving '" + name + "'");
// }
// bytes = getAtAspectJAspectBytes(name, bytes);
+
+ // Add the weaved class to the cache only if there
+ // has been an actual change
+ // JVK: Is there a better way to check if the class has
+ // been transformed without carrying up some value
+ // from the depths?
+ if (cacheKey != null) {
+ // If no transform has been applied, mark the class
+ // as ignored.
+ if (Arrays.equals(original_bytes, bytes)) {
+ cache.ignore(cacheKey);
+ } else {
+ cache.put(cacheKey, bytes);
+ }
+ }
} else if (debugOn) {
debug("not weaving '" + name + "'");
}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CacheBacking.java b/weaver/src/org/aspectj/weaver/tools/cache/CacheBacking.java
new file mode 100644
index 000000000..db3693dec
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CacheBacking.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * Interface for the backing to the cache; usually a file,
+ * but could be an in-memory backing for testing.
+ * <p/>
+ * aspectj and jvmti provide no suitable guarantees
+ * on locking for class redefinitions, so every implementation
+ * must have a some locking mechanism to prevent invalid reads.
+ */
+public interface CacheBacking {
+ /**
+ * Return a list of keys which match the given
+ * regex.
+ *
+ * @param regex
+ * @return
+ */
+ public String[] getKeys(String regex);
+
+ /**
+ * Remove an entry from the cache
+ *
+ * @param ref
+ */
+ public void remove(CachedClassReference ref);
+
+ /**
+ * Clear the entire cache
+ */
+ public void clear();
+
+ /**
+ * Get a cache entry
+ *
+ * @param ref entry to retrieve
+ * @return the cached bytes or null, if the entry does not exist
+ */
+ public CachedClassEntry get(CachedClassReference ref);
+
+ /**
+ * Put an entry in the cache
+ *
+ * @param entry key of the entry
+ */
+ public void put(CachedClassEntry entry);
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CacheFactory.java b/weaver/src/org/aspectj/weaver/tools/cache/CacheFactory.java
new file mode 100644
index 000000000..042ef61bd
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CacheFactory.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * Facility for overriding the default CacheKeyResolver
+ * and CacheBacking; an implementing factory must be set
+ * on the {@link WeavedClassCache} before the
+ * {@link org.aspectj.weaver.tools.WeavingAdaptor} is
+ * configured.
+ */
+public interface CacheFactory {
+ CacheKeyResolver createResolver();
+
+ CacheBacking createBacking(String scope);
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CacheKeyResolver.java b/weaver/src/org/aspectj/weaver/tools/cache/CacheKeyResolver.java
new file mode 100644
index 000000000..8c76ad878
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CacheKeyResolver.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import java.util.List;
+
+/**
+ * Interface to allow alternate hashing schemes for weaved and
+ * generated classes. While the DefaultCacheKeyResolver may be
+ * a reasonable naive implementation, the management and invalidation
+ * of the cache may be more usefully accomplished at the Application
+ * or Container level.
+ * <p/>
+ * The key is not a one-way hash; it must be convertible back to a
+ * className and must match the regex for the type of key it is
+ * (generated or weaved).
+ */
+public interface CacheKeyResolver {
+ /**
+ * Create a key for the given className from a class generated by
+ * the weaver such that:
+ * <pre>
+ * className == keyToClass(generatedKey(className)) holds
+ * and
+ * generatedKey(className).matches(getGeneratedRegex()) == true
+ * </pre>
+ *
+ * @param className class to create a key for
+ * @return key for the class, or null if no caching should be performed
+ */
+ CachedClassReference generatedKey(String className);
+
+ /**
+ * Create a key for the given class name and byte array from the pre-weaved
+ * class such that
+ * <pre>
+ * className == keyToClass(weavedKey(className, various_bytes)) holds
+ * and
+ * weavedKey(className, various_bytes).matches(getWeavedRegex()) == true
+ * </pre>
+ *
+ * @param className class to create a key for
+ * @param original_bytes bytes of the pre-weaved class
+ * @return key for the class, or null if no caching should be performed
+ */
+ CachedClassReference weavedKey(String className, byte[] original_bytes);
+
+ /**
+ * Convert a key back to a className
+ *
+ * @param key cache key
+ * @return className
+ */
+ String keyToClass(String key);
+
+ /**
+ * Create a unique string for the given classpath and aspect list
+ *
+ * @param loader Classloader for this adapter
+ * @param aspects list of aspects; either urls or class names handled by this adapter
+ * @return scope, or null, if no caching should be performed for this classloader
+ */
+ String createClassLoaderScope(ClassLoader loader, List<String> aspects);
+
+ /**
+ * Return a regex which matches all generated keys
+ *
+ * @return string regex
+ */
+ String getGeneratedRegex();
+
+ /**
+ * Return a regex which matches all weaved keys;
+ *
+ * @return string regex
+ */
+ String getWeavedRegex();
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CacheStatistics.java b/weaver/src/org/aspectj/weaver/tools/cache/CacheStatistics.java
new file mode 100644
index 000000000..3a1d4d7ad
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CacheStatistics.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * Maintains some basic statistics on the class cache.
+ */
+public class CacheStatistics {
+ private volatile int hits;
+ private volatile int misses;
+ private volatile int weaved;
+ private volatile int generated;
+ private volatile int ignored;
+ private volatile int puts;
+ private volatile int puts_ignored;
+
+ public void hit() {
+ hits++;
+ }
+
+ public void miss() {
+ misses++;
+ }
+
+ public void weaved() {
+ weaved++;
+ }
+
+ public void generated() {
+ generated++;
+ }
+
+ public void ignored() {
+ ignored++;
+ }
+
+ public void put() {
+ puts++;
+ }
+
+ public void putIgnored() {
+ puts_ignored++;
+ }
+
+
+ public int getHits() {
+ return hits;
+ }
+
+ public int getMisses() {
+ return misses;
+ }
+
+ public int getWeaved() {
+ return weaved;
+ }
+
+ public int getGenerated() {
+ return generated;
+ }
+
+ public int getIgnored() {
+ return ignored;
+ }
+
+ public int getPuts() {
+ return puts;
+ }
+
+ public int getPutsIgnored() {
+ return puts_ignored;
+ }
+
+
+ public void reset() {
+ hits = 0;
+ misses = 0;
+ weaved = 0;
+ generated = 0;
+ ignored = 0;
+ puts = 0;
+ puts_ignored = 0;
+ }
+
+ public String toString() {
+ return "CacheStatistics{" +
+ "hits=" + hits +
+ ", misses=" + misses +
+ ", weaved=" + weaved +
+ ", generated=" + generated +
+ ", ignored=" + ignored +
+ ", puts=" + puts +
+ ", puts_ignored=" + puts_ignored +
+ '}';
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CachedClassEntry.java b/weaver/src/org/aspectj/weaver/tools/cache/CachedClassEntry.java
new file mode 100644
index 000000000..9ff7666b6
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CachedClassEntry.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * Represents a class which has been cached
+ */
+public class CachedClassEntry {
+ enum EntryType {
+ GENERATED,
+ WEAVED,
+ IGNORED,
+ }
+
+ private final CachedClassReference ref;
+ private final byte[] bytes;
+ private final EntryType type;
+
+ public CachedClassEntry(CachedClassReference ref, byte[] bytes, EntryType type) {
+ this.bytes = bytes;
+ this.ref = ref;
+ this.type = type;
+ }
+
+ public String getClassName() {
+ return ref.getClassName();
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public String getKey() {
+ return ref.getKey();
+ }
+
+ public boolean isGenerated() {
+ return type == EntryType.GENERATED;
+ }
+
+ public boolean isWeaved() {
+ return type == EntryType.WEAVED;
+ }
+
+ public boolean isIgnored() {
+ return type == EntryType.IGNORED;
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/CachedClassReference.java b/weaver/src/org/aspectj/weaver/tools/cache/CachedClassReference.java
new file mode 100644
index 000000000..06a0a97eb
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/CachedClassReference.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * A typed reference to a cached class entry. The key to any
+ * cache entry is a simple string, but that string may contain
+ * some specialized encoding. This class handles all of that
+ * encoding.
+ * <p/>
+ * External users of the cache should not be able to create these
+ * objects manually.
+ */
+public class CachedClassReference {
+ enum EntryType {
+ GENERATED,
+ WEAVED,
+ IGNORED,
+ }
+
+ private final String key;
+ private final String className;
+
+ protected CachedClassReference(String key, CacheKeyResolver resolver) {
+ this.key = key;
+ this.className = resolver.keyToClass(key);
+ }
+
+ /**
+ * Protected to allow only the WeavedClassCache initialization rights
+ *
+ * @param key encoded key of the class
+ * @param className the classname
+ */
+ protected CachedClassReference(String key, String className) {
+ this.key = key;
+ this.className = className;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheFactory.java b/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheFactory.java
new file mode 100644
index 000000000..0c0d29ca9
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheFactory.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+/**
+ * Default factory for creating the backing and resolving classes.
+ */
+public class DefaultCacheFactory implements CacheFactory {
+ public CacheKeyResolver createResolver() {
+ return new DefaultCacheKeyResolver();
+ }
+
+ public CacheBacking createBacking(String scope) {
+ return DefaultFileCacheBacking.createBacking(scope, createResolver());
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolver.java b/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolver.java
new file mode 100644
index 000000000..7383816b0
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolver.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * Naive default class and classloader hashing implementation useful
+ * for some multi-classloader environments.
+ * <p/>
+ * This implementation creates classloader scopes of the form:<br/>
+ * "ExampleClassLoaderName.[crc hash]"
+ * <p/>
+ * And weaved class keys of the form:<br/>
+ * "com.foo.BarClassName.[bytes len][crc].weaved"
+ * <p/>
+ * And generated class keys of the form:<br/>
+ * "com.foo.BarClassName$AjClosure.generated
+ */
+public class DefaultCacheKeyResolver implements CacheKeyResolver {
+ public static final String GENERATED_SUFFIX = ".generated";
+ public static final String WEAVED_SUFFIX = ".weaved";
+
+ /**
+ * Create a scope from a set of urls and aspect urls. Creates scope
+ * of the form "ExampleClassLoaderName.[md5sum]" or
+ * "ExampleClassLoaderName.[crc]"
+ *
+ * @param cl the classloader which uses the cache, can be null
+ * @param aspects the aspects
+ * @return a unique string for URLClassloaders, otherwise a non-unique classname
+ */
+ public String createClassLoaderScope(ClassLoader cl, List<String> aspects) {
+ String name = cl != null ? cl.getClass().getSimpleName() : "unknown";
+
+ List<String> hashableStrings = new LinkedList<String>();
+ StringBuilder hashable = new StringBuilder(256);
+
+ // Add the list of loader urls to the hash list
+ if (cl != null && cl instanceof URLClassLoader) {
+ URL[] urls = ((URLClassLoader) cl).getURLs();
+ for (int i = 0; i < urls.length; i++) {
+ hashableStrings.add(urls[i].toString());
+ }
+ }
+
+ hashableStrings.addAll(aspects);
+ Collections.sort(hashableStrings);
+ for (Iterator<String> it = hashableStrings.iterator(); it.hasNext(); ) {
+ String url = it.next();
+ hashable.append(url);
+ }
+ String hash = null;
+ byte[] bytes = hashable.toString().getBytes();
+ hash = crc(bytes);
+
+ return name + '.' + hash;
+ }
+
+ private String crc(byte[] input) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(input);
+ return String.valueOf(crc32.getValue());
+ }
+
+ public String getGeneratedRegex() {
+ return ".*" + GENERATED_SUFFIX;
+ }
+
+ public String getWeavedRegex() {
+ return ".*" + WEAVED_SUFFIX;
+ }
+
+
+ /**
+ * Converts a cache key back to a className
+ *
+ * @param key to convert
+ * @return className, e.g. "com.foo.Bar"
+ */
+ public String keyToClass(String key) {
+ if (key.endsWith(GENERATED_SUFFIX)) {
+ return key.replaceAll(GENERATED_SUFFIX + "$", "");
+ }
+ if (key.endsWith(WEAVED_SUFFIX)) {
+ return key.replaceAll("\\.[^.]+" + WEAVED_SUFFIX, "");
+ }
+ return key;
+ }
+
+ public CachedClassReference weavedKey(String className, byte[] original_bytes) {
+ String hash = crc(original_bytes);
+ return new CachedClassReference(className + "." + hash + WEAVED_SUFFIX, className);
+
+ }
+
+ public CachedClassReference generatedKey(String className) {
+ return new CachedClassReference(className + GENERATED_SUFFIX, className);
+ }
+
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/DefaultFileCacheBacking.java b/weaver/src/org/aspectj/weaver/tools/cache/DefaultFileCacheBacking.java
new file mode 100644
index 000000000..8959ee442
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/DefaultFileCacheBacking.java
@@ -0,0 +1,309 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.FileUtil;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.zip.CRC32;
+
+
+/**
+ * Naive File-Backed Class Cache with no expiry or application
+ * centric invalidation.
+ * <p/>
+ * Enabled with the system property, "aj.weaving.cache.dir"
+ * If this system property is not set, no caching will be
+ * performed.
+ * <p/>
+ * A CRC checksum is stored alongside the class file to verify
+ * the bytes on read. If for some reason there is an error
+ * reading either the class or crc file, or if the crc does not
+ * match the class data the cache entry is deleted.
+ * <p/>
+ * An alternate implementation of this could store the class file
+ * as a jar/zip directly, which would have the required crc; as
+ * a first pass however it is somewhat useful to view these files
+ * in expanded form for debugging.
+ */
+public class DefaultFileCacheBacking implements CacheBacking {
+ public static final String WEAVED_CLASS_CACHE_DIR = "aj.weaving.cache.dir";
+ public static final String INDEX_FILE = "cache.idx";
+
+ public static class IndexEntry implements Serializable {
+ public String key;
+ public boolean generated;
+ public boolean ignored;
+ public long crc;
+ }
+
+ private final File cacheDirectory;
+ private final CacheKeyResolver resolver;
+ private final HashMap<String, IndexEntry> index = new HashMap<String, IndexEntry>();
+
+ private static final Object LOCK = new Object();
+
+ protected DefaultFileCacheBacking(File cacheDirectory, CacheKeyResolver resolver) {
+ this.cacheDirectory = cacheDirectory;
+ this.resolver = resolver;
+ readIndex();
+ }
+
+ public static CacheBacking createBacking(File cacheDir, CacheKeyResolver resolver) {
+ if (!cacheDir.exists()) {
+ if (!cacheDir.mkdirs()) {
+ MessageUtil.error("Unable to create cache directory at " + cacheDir.getName());
+ return null;
+ }
+ }
+ if (!cacheDir.canWrite()) {
+ MessageUtil.error("Cache directory is not writable at " + cacheDir.getName());
+ return null;
+ }
+ return new DefaultFileCacheBacking(cacheDir, resolver);
+ }
+
+ public static IndexEntry[] readIndex(File indexFile) {
+ IndexEntry[] iea = new IndexEntry[0];
+ FileInputStream fis = null;
+ ObjectInputStream ois = null;
+ try {
+ if (!indexFile.canRead()) {
+ return iea;
+ }
+ fis = new FileInputStream(indexFile);
+ ois = new ObjectInputStream(fis);
+ iea = (IndexEntry[]) ois.readObject();
+ } catch (Exception e) {
+ delete(indexFile);
+ } finally {
+ close(fis, indexFile);
+ close(ois, indexFile);
+ }
+ return iea;
+ }
+
+ private void readIndex() {
+ synchronized (LOCK) {
+ IndexEntry[] idx = readIndex(new File(cacheDirectory, INDEX_FILE));
+ for (IndexEntry ie : idx) {
+ File cacheFile = new File(cacheDirectory, ie.key);
+ if (cacheFile.canRead() || ie.ignored) {
+ index.put(ie.key, ie);
+ }
+ }
+ }
+ }
+
+ private void writeIndex() {
+ synchronized (LOCK) {
+ if (!cacheDirectory.exists())
+ cacheDirectory.mkdirs();
+ File indexFile = new File(cacheDirectory, INDEX_FILE);
+ FileOutputStream fos = null;
+ ObjectOutputStream oos = null;
+ try {
+ delete(indexFile);
+ fos = new FileOutputStream(indexFile);
+ oos = new ObjectOutputStream(fos);
+ oos.writeObject(index.values().toArray(new IndexEntry[0]));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ close(fos, indexFile);
+ close(oos, indexFile);
+ }
+ }
+ }
+
+ private void removeIndexEntry(String key) {
+ synchronized (LOCK) {
+ index.remove(key);
+ writeIndex();
+ }
+ }
+
+ private void addIndexEntry(IndexEntry ie) {
+ synchronized (LOCK) {
+ index.put(ie.key, ie);
+ writeIndex();
+ }
+ }
+
+ public void clear() {
+ synchronized (LOCK) {
+ FileUtil.deleteContents(cacheDirectory);
+ }
+ }
+
+ public static CacheBacking createBacking(String scope, CacheKeyResolver resolver) {
+ String cache = System.getProperty(WEAVED_CLASS_CACHE_DIR);
+ if (cache == null) {
+ return null;
+ }
+
+ File cacheDir = new File(cache, scope);
+ return createBacking(cacheDir, resolver);
+ }
+
+ public String[] getKeys(final String regex) {
+ File[] files = cacheDirectory.listFiles(new FilenameFilter() {
+ public boolean accept(File file, String s) {
+ if (s.matches(regex)) {
+ return true;
+ }
+ return false;
+ }
+ });
+ if (files == null) {
+ return new String[0];
+ }
+ String[] keys = new String[files.length];
+ for (int i = 0; i < files.length; i++) {
+ keys[i] = files[i].getName();
+ }
+ return keys;
+ }
+
+ public CachedClassEntry get(CachedClassReference ref) {
+ IndexEntry ie = index.get(ref.getKey());
+ if (ie != null && ie.ignored) {
+ return new CachedClassEntry(ref, WeavedClassCache.ZERO_BYTES, CachedClassEntry.EntryType.IGNORED);
+ }
+ File cacheFile = new File(cacheDirectory, ref.getKey());
+ if (cacheFile.canRead()) {
+ if (ie == null) {
+ // no index, delete
+ delete(cacheFile);
+ return null;
+ }
+ byte[] bytes = read(cacheFile, ie.crc);
+ if (bytes != null) {
+ if (!ie.generated) {
+ return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.WEAVED);
+ } else {
+ return new CachedClassEntry(ref, bytes, CachedClassEntry.EntryType.GENERATED);
+ }
+ }
+ }
+ return null;
+ }
+
+ public void put(CachedClassEntry entry) {
+ File cacheFile = new File(cacheDirectory, entry.getKey());
+ if (!cacheFile.exists()) {
+ IndexEntry ie = new IndexEntry();
+ ie.key = entry.getKey();
+ ie.generated = entry.isGenerated();
+ ie.ignored = entry.isIgnored();
+ if (!entry.isIgnored()) {
+ ie.crc = write(cacheFile, entry.getBytes());
+ }
+ addIndexEntry(ie);
+ }
+ }
+
+ public void remove(CachedClassReference ref) {
+ synchronized (LOCK) {
+ File cacheFile = new File(cacheDirectory, ref.getKey());
+ removeIndexEntry(ref.getKey());
+ delete(cacheFile);
+ }
+ }
+
+ protected byte[] read(File file, long expectedCRC) {
+ CRC32 checksum = new CRC32();
+ synchronized (LOCK) {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ byte[] bytes = FileUtil.readAsByteArray(fis);
+ checksum.update(bytes);
+ if (checksum.getValue() == expectedCRC) {
+ return bytes;
+ }
+ } catch (FileNotFoundException e) {
+ // file disappeared
+ MessageUtil.error("File not found " + file.getName());
+ } catch (IOException e) {
+ MessageUtil.error("Error reading cached class " + e.getLocalizedMessage());
+ } finally {
+ close(fis, file);
+ }
+ // delete the file if there was an exception reading it
+ // or the expected checksum does not match
+ delete(file);
+ }
+ return null;
+ }
+
+ protected long write(File file, byte[] bytes) {
+ if (file.exists()) {
+ return -1;
+ }
+ synchronized (LOCK) {
+ if (file.exists()) {
+ return -1;
+ }
+ OutputStream out = null;
+ ObjectOutputStream crcOut = null;
+ CRC32 checksum = new CRC32();
+ try {
+ out = new FileOutputStream(file);
+ out.write(bytes);
+ checksum.update(bytes);
+ return checksum.getValue();
+ } catch (FileNotFoundException e) {
+ MessageUtil.error("Error writing (File Not Found) " + file.getName() + ": " + e.getLocalizedMessage());
+ } catch (IOException e) {
+ MessageUtil.error("Error writing " + file.getName());
+
+ } finally {
+ close(out, file);
+ }
+ // delete the file if there was an exception writing it
+ delete(file);
+ }
+ return -1;
+ }
+
+ protected static void delete(File file) {
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ protected static void close(OutputStream out, File file) {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // error
+ MessageUtil.error("Error closing write file " + file.getName());
+ }
+ }
+ }
+
+ protected static void close(InputStream in, File file) {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // error
+ MessageUtil.error("Error closing read file " + file.getName());
+ }
+ }
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/GeneratedCachedClassHandler.java b/weaver/src/org/aspectj/weaver/tools/cache/GeneratedCachedClassHandler.java
new file mode 100644
index 000000000..a89936a39
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/GeneratedCachedClassHandler.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import org.aspectj.weaver.tools.GeneratedClassHandler;
+
+/**
+ * Handler for generated classes; such as Shadowed closures, etc. This wraps the normal
+ * generated class handler with caching
+ */
+public class GeneratedCachedClassHandler implements GeneratedClassHandler {
+ private final WeavedClassCache cache;
+ private final GeneratedClassHandler nextGeneratedClassHandler;
+
+ public GeneratedCachedClassHandler(WeavedClassCache cache, GeneratedClassHandler nextHandler) {
+ this.cache = cache;
+ this.nextGeneratedClassHandler = nextHandler;
+ }
+
+ public void acceptClass(String name, byte[] bytes) {
+ // The cache expects classNames in dot form
+ CachedClassReference ref = cache.createGeneratedCacheKey(name.replace('/', '.'));
+ cache.put(ref, bytes);
+ if (nextGeneratedClassHandler != null) {
+ nextGeneratedClassHandler.acceptClass(name, bytes);
+ }
+ }
+}
diff --git a/weaver/src/org/aspectj/weaver/tools/cache/WeavedClassCache.java b/weaver/src/org/aspectj/weaver/tools/cache/WeavedClassCache.java
new file mode 100644
index 000000000..eaaaffcff
--- /dev/null
+++ b/weaver/src/org/aspectj/weaver/tools/cache/WeavedClassCache.java
@@ -0,0 +1,327 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.Message;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.weaver.tools.GeneratedClassHandler;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Manages a cache of weaved and generated classes similar to Eclipse Equinox,
+ * except designed to operate across multiple restarts of the JVM and with one
+ * cache per classloader; allowing URLClassLoaders with the same set of URI
+ * paths to share the same cache (with the default configuration).
+ * <p/>
+ * To enable the default configuration two system properties must be set:
+ * <pre>
+ * "-Daj.weaving.cache.enabled=true"
+ * "-Daj.weaving.cache.dir=/some/directory"
+ * </pre>
+ * <p/>
+ * The class cache is often something that application developers or
+ * containers would like to manage, so there are a few interfaces for overriding the
+ * default behavior and performing other management functions.
+ * <p/>
+ * {@link CacheBacking} <br/>
+ * Provides an interface for implementing a custom backing store
+ * for the cache; The default implementation in {@link DefaultFileCacheBacking}
+ * provides a naive file-backed cache. An alternate implementation may ignore
+ * caching until signaled explicitly by the application, or only cache files
+ * for a specific duration. This class delegates the locking and synchronization
+ * requirements to the CacheBacking implementation.
+ * <p/>
+ * {@link CacheKeyResolver} <br/>
+ * Provides methods for creating keys from classes to be cached and for
+ * creating the "scope" of the cache itself for a given classloader and aspect
+ * list. The default implementation is provided by {@link DefaultCacheKeyResolver}
+ * but an alternate implementation may want to associate a cache with a particular
+ * application running underneath a container.
+ * <p/>
+ * This naive cache does not normally invalidate *any* classes; the interfaces above
+ * must be used to implement more intelligent behavior. Cache invalidation
+ * problems may occur in at least three scenarios:
+ * <pre>
+ * 1. New aspects are added dynamically somewhere in the classloader hierarchy; affecting
+ * other classes elsewhere.
+ * 2. Use of declare parent in aspects to change the type hierarchy; if the cache
+ * has not invalidated the right classes in the type hierarchy aspectj may not
+ * be reconstruct the class incorrectly.
+ * 3. Similarly to (2), the addition of fields or methods on classes which have
+ * already been weaved and cached could have inter-type conflicts.
+ * </pre>
+ */
+public class WeavedClassCache {
+ public static final String WEAVED_CLASS_CACHE_ENABLED = "aj.weaving.cache.enabled";
+ private static CacheFactory DEFAULT_FACTORY = new DefaultCacheFactory();
+ public static final byte[] ZERO_BYTES = new byte[0];
+ private final IMessageHandler messageHandler;
+ private final GeneratedCachedClassHandler cachingClassHandler;
+ private final CacheBacking backing;
+ private final CacheStatistics stats;
+ private final CacheKeyResolver resolver;
+ private final String name;
+
+ private static final List<WeavedClassCache> cacheRegistry = new LinkedList<WeavedClassCache>();
+
+ protected WeavedClassCache(GeneratedClassHandler existingClassHandler,
+ IMessageHandler messageHandler,
+ String name,
+ CacheBacking backing,
+ CacheKeyResolver resolver) {
+ this.resolver = resolver;
+ this.name = name;
+ this.backing = backing;
+ this.messageHandler = messageHandler;
+ initializeGenerated(existingClassHandler);
+ // wrap the existing class handler with a caching version
+ cachingClassHandler = new GeneratedCachedClassHandler(this, existingClassHandler);
+ this.stats = new CacheStatistics();
+ synchronized (cacheRegistry) {
+ cacheRegistry.add(this);
+ }
+ }
+
+ /**
+ * Creates a new cache using the resolver and backing returned by the DefaultCacheFactory.
+ *
+ * @param loader classloader for this cache
+ * @param aspects list of aspects used by the WeavingAdapter
+ * @param existingClassHandler the existing GeneratedClassHandler used by the weaver
+ * @param messageHandler the existing messageHandler used by the weaver
+ * @return
+ */
+ public static WeavedClassCache createCache(ClassLoader loader, List<String> aspects, GeneratedClassHandler existingClassHandler, IMessageHandler messageHandler) {
+ CacheKeyResolver resolver = DEFAULT_FACTORY.createResolver();
+ String name = resolver.createClassLoaderScope(loader, aspects);
+ if (name == null) {
+ return null;
+ }
+ CacheBacking backing = DEFAULT_FACTORY.createBacking(name);
+ if (backing != null) {
+ return new WeavedClassCache(existingClassHandler, messageHandler, name, backing, resolver);
+ }
+ return null;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The Cache and be extended in two ways, through a specialized CacheKeyResolver and
+ * a specialized CacheBacking. The default factory used to create these classes can
+ * be set with this method. Since each weaver will create a cache, this method must be
+ * called before the weaver is first initialized.
+ *
+ * @param factory
+ */
+ public static void setDefaultCacheFactory(CacheFactory factory) {
+ DEFAULT_FACTORY = factory;
+ }
+
+ /**
+ * Created a key for a generated class
+ *
+ * @param className ClassName, e.g. "com.foo.Bar"
+ * @return the cache key, or null if no caching should be performed
+ */
+ public CachedClassReference createGeneratedCacheKey(String className) {
+ return resolver.generatedKey(className);
+ }
+
+ /**
+ * Create a key for a normal weaved class
+ *
+ * @param className ClassName, e.g. "com.foo.Bar"
+ * @param originalBytes Original byte array of the class
+ * @return a cache key, or null if no caching should be performed
+ */
+ public CachedClassReference createCacheKey(String className, byte[] originalBytes) {
+ return resolver.weavedKey(className, originalBytes);
+ }
+
+ /**
+ * Returns a generated class handler which wraps the handler this cache was initialized
+ * with; this handler should be used to make sure that generated classes are added
+ * to the cache
+ */
+ public GeneratedClassHandler getCachingClassHandler() {
+ return cachingClassHandler;
+ }
+
+ /**
+ * Has caching been enabled through the System property,
+ * WEAVED_CLASS_CACHE_ENABLED
+ *
+ * @return true if caching is enabled
+ */
+ public static boolean isEnabled() {
+ return System.getProperty(WEAVED_CLASS_CACHE_ENABLED) != null;
+ }
+
+ /**
+ * Put a class in the cache
+ *
+ * @param ref reference to the entry, as created through createCacheKey
+ * @param weavedBytes bytes to cache
+ */
+ public void put(CachedClassReference ref, byte[] weavedBytes) {
+ CachedClassEntry.EntryType type = CachedClassEntry.EntryType.WEAVED;
+ if (ref.getKey().matches(resolver.getGeneratedRegex())) {
+ type = CachedClassEntry.EntryType.GENERATED;
+ }
+ backing.put(new CachedClassEntry(ref, weavedBytes, type));
+ stats.put();
+ }
+
+ /**
+ * Get a cache value
+ *
+ * @param ref reference to the cache entry, created through createCacheKey
+ * @return the CacheEntry, or null if no entry exists in the cache
+ */
+ public CachedClassEntry get(CachedClassReference ref) {
+ CachedClassEntry entry = backing.get(ref);
+ if (entry == null) {
+ stats.miss();
+ } else {
+ stats.hit();
+ if (entry.isGenerated()) stats.generated();
+ if (entry.isWeaved()) stats.weaved();
+ if (entry.isIgnored()) stats.ignored();
+ }
+ return entry;
+ }
+
+ /**
+ * Put a cache entry to indicate that the class should not be
+ * weaved; the original bytes of the class should be used.
+ *
+ * @param ref
+ */
+ public void ignore(CachedClassReference ref) {
+ stats.putIgnored();
+ backing.put(new CachedClassEntry(ref, ZERO_BYTES, CachedClassEntry.EntryType.IGNORED));
+ }
+
+ /**
+ * Invalidate a cache entry
+ *
+ * @param ref
+ */
+ public void remove(CachedClassReference ref) {
+ backing.remove(ref);
+ }
+
+ /**
+ * Clear the entire cache
+ */
+ public void clear() {
+ backing.clear();
+ }
+
+ /**
+ * Get the statistics associated with this cache, or
+ * null if statistics have not been enabled.
+ *
+ * @return
+ */
+ public CacheStatistics getStats() {
+ return stats;
+ }
+
+ /**
+ * Return a list of all WeavedClassCaches which have been initialized
+ *
+ * @return
+ */
+ public static List<WeavedClassCache> getCaches() {
+ synchronized (cacheRegistry) {
+ return new LinkedList<WeavedClassCache>(cacheRegistry);
+ }
+ }
+
+ /**
+ * Get all generated classes which have been cached
+ *
+ * @return
+ */
+ protected CachedClassEntry[] getGeneratedClasses() {
+ return getEntries(resolver.getGeneratedRegex());
+ }
+
+ /**
+ * Get all weaved classes which have been cached
+ *
+ * @return
+ */
+ protected CachedClassEntry[] getWeavedClasses() {
+ return getEntries(resolver.getWeavedRegex());
+ }
+
+ /**
+ * For each cached, generated class, pass that class through the given
+ * GeneratedClassHandler, typically which defines that handler within the
+ * current ClassLoader.
+ *
+ * @param handler class handler
+ */
+ private void initializeGenerated(GeneratedClassHandler handler) {
+ if (handler == null)
+ return;
+ CachedClassEntry[] classes = getGeneratedClasses();
+ for (CachedClassEntry entry : classes) {
+
+ handler.acceptClass(entry.getClassName(), entry.getBytes());
+ }
+ }
+
+ /**
+ * Gets an array of CacheClassEntries with the given regex
+ *
+ * @param regex filter
+ * @return array of entries
+ */
+ protected CachedClassEntry[] getEntries(String regex) {
+ String[] keys = backing.getKeys(regex);
+ List<CachedClassEntry> entries = new LinkedList<CachedClassEntry>();
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ CachedClassReference ref = new CachedClassReference(key, resolver);
+ CachedClassEntry entry = backing.get(ref);
+ if (entry != null) {
+ entries.add(entry);
+ }
+ }
+ return entries.toArray(new CachedClassEntry[entries.size()]);
+ }
+
+ protected void error(String message, Throwable th) {
+ messageHandler.handleMessage(new Message(message, IMessage.ERROR, th, null));
+ }
+
+ protected void error(String message) {
+ MessageUtil.error(messageHandler, message);
+ }
+
+ protected void info(String message) {
+ MessageUtil.info(message);
+ }
+
+}
diff --git a/weaver/testsrc/org/aspectj/weaver/BcweaverModuleTests.java b/weaver/testsrc/org/aspectj/weaver/BcweaverModuleTests.java
index 1f1997864..b0e019bbc 100644
--- a/weaver/testsrc/org/aspectj/weaver/BcweaverModuleTests.java
+++ b/weaver/testsrc/org/aspectj/weaver/BcweaverModuleTests.java
@@ -18,6 +18,7 @@ import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.aspectj.weaver.tools.ToolsTests;
+import org.aspectj.weaver.tools.cache.CacheTests;
public class BcweaverModuleTests extends TestCase {
@@ -29,6 +30,7 @@ public class BcweaverModuleTests extends TestCase {
suite.addTestSuite(LocaleTest.class);
suite.addTestSuite(GenericSignatureParserTest.class);
suite.addTest(ToolsTests.suite());
+ suite.addTest(CacheTests.suite());
return suite;
}
diff --git a/weaver/testsrc/org/aspectj/weaver/tools/cache/CacheTests.java b/weaver/testsrc/org/aspectj/weaver/tools/cache/CacheTests.java
new file mode 100644
index 000000000..a87b1eba3
--- /dev/null
+++ b/weaver/testsrc/org/aspectj/weaver/tools/cache/CacheTests.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ */
+public class CacheTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite(CacheTests.class.getName());
+ suite.addTestSuite(WeavedClassCacheTest.class);
+ suite.addTestSuite(DefaultCacheKeyResolverTest.class);
+ suite.addTestSuite(DefaultFileCacheBackingTest.class);
+ return suite;
+ }
+}
diff --git a/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolverTest.java b/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolverTest.java
new file mode 100644
index 000000000..8dbe31463
--- /dev/null
+++ b/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolverTest.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import junit.framework.TestCase;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ */
+public class DefaultCacheKeyResolverTest extends TestCase {
+ byte[] FAKE_BYTES = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ String FAKE_CLASS = "com.example.foo.Bar";
+
+ DefaultCacheKeyResolver resolver = new DefaultCacheKeyResolver();
+
+ class BasicTestCL extends ClassLoader {
+ }
+
+ class URLTestCL extends URLClassLoader {
+ public URLTestCL(URL... urls) {
+ super(urls);
+ }
+ }
+
+
+ public void testNonURLClassLoaderScope() throws Exception {
+ String scope = resolver.createClassLoaderScope(new BasicTestCL(), Collections.<String>emptyList());
+ assertTrue(scope.startsWith(BasicTestCL.class.getSimpleName()));
+ }
+
+ public void testCreateURLClassLoaderScope() throws Exception {
+ URL testURLA = new URL("http://example.com");
+ URL testURLB = new URL("file:///tmp");
+ URL testURLC = new URL("ftp://ftp.example.com");
+ URLTestCL A = new URLTestCL(testURLA);
+ URLTestCL AB = new URLTestCL(testURLA, testURLB);
+ URLTestCL BC = new URLTestCL(testURLB, testURLC);
+ URLTestCL BC2 = new URLTestCL(testURLC, testURLB);
+ String[] a = {"one", "two", "three", "four"};
+ String[] a2 = {"one", "two", "three"};
+ String scopeAa = resolver.createClassLoaderScope(A, Arrays.asList(a));
+ String scopeABa = resolver.createClassLoaderScope(AB, Arrays.asList(a));
+ String scopeBCa = resolver.createClassLoaderScope(BC, Arrays.asList(a));
+ String scopeBC2a = resolver.createClassLoaderScope(BC2, Arrays.asList(a));
+ String scopeAa2 = resolver.createClassLoaderScope(A, Arrays.asList(a2));
+ String scopeABa2 = resolver.createClassLoaderScope(AB, Arrays.asList(a2));
+ String scopeBCa2 = resolver.createClassLoaderScope(BC, Arrays.asList(a2));
+ String scopeBC2a2 = resolver.createClassLoaderScope(BC2, Arrays.asList(a2));
+
+ assertFalse(scopeAa.equals(scopeABa));
+ assertFalse(scopeAa.equals(scopeBCa));
+ assertFalse(scopeABa.equals(scopeBCa));
+ assertTrue(scopeBC2a.equals(scopeBCa));
+ assertFalse(scopeAa.equals(scopeAa2));
+ assertFalse(scopeABa.equals(scopeABa2));
+ assertFalse(scopeBCa.equals(scopeBCa2));
+ assertFalse(scopeBC2a.equals(scopeBC2a2));
+
+
+ }
+
+
+ public void testCreateGeneratedCacheKey() throws Exception {
+ CachedClassReference ref = resolver.generatedKey(FAKE_CLASS);
+ assertTrue(ref.getKey().startsWith(FAKE_CLASS));
+ assertTrue(ref.getKey().matches(resolver.getGeneratedRegex()));
+ assertEquals(FAKE_CLASS, resolver.keyToClass(ref.getKey()));
+ }
+
+ public void testCreateCacheKey() throws Exception {
+ // crc hashing
+ CachedClassReference ref = resolver.weavedKey(FAKE_CLASS, FAKE_BYTES);
+ assertTrue("key " + ref.getKey() + " does not match " + resolver.getWeavedRegex(), ref.getKey().matches(resolver.getWeavedRegex()));
+ String className = resolver.keyToClass(ref.getKey());
+ assertEquals("class " + FAKE_CLASS + " != " + className, FAKE_CLASS, className);
+ }
+
+}
diff --git a/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultFileCacheBackingTest.java b/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultFileCacheBackingTest.java
new file mode 100644
index 000000000..88dce0d98
--- /dev/null
+++ b/weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultFileCacheBackingTest.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import junit.framework.TestCase;
+import org.aspectj.util.FileUtil;
+
+import java.io.File;
+import java.util.zip.CRC32;
+
+/**
+ */
+public class DefaultFileCacheBackingTest extends TestCase {
+ File root = null;
+ byte[] FAKE_BYTES = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ String FAKE_CLASS = "com.example.foo.Bar";
+ CacheKeyResolver resolver = new DefaultCacheKeyResolver();
+ CachedClassReference fakeRef = resolver.weavedKey(FAKE_CLASS, FAKE_BYTES);
+
+
+ public void setUp() throws Exception {
+ if (root == null) {
+ File tempFile = File.createTempFile("aspectj", "test");
+ File tempDir = tempFile.getParentFile();
+ root = new File(tempDir, "aspectj-test-cache");
+ }
+ }
+
+ public void tearDown() throws Exception {
+ FileUtil.deleteContents(root);
+ root = null;
+ }
+
+ public void testCreateBacking() throws Exception {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ assertNotNull(backing);
+ assertTrue(root.exists());
+ assertTrue(root.isDirectory());
+ }
+
+ public void testClear() {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ backing.put(new CachedClassEntry(fakeRef, FAKE_BYTES, CachedClassEntry.EntryType.WEAVED));
+ assertNotNull(backing.get(fakeRef));
+ backing.clear();
+ assertNull(backing.get(fakeRef));
+ }
+
+ private CachedClassEntry createTestEntry(String key) {
+ return new CachedClassEntry(new CachedClassReference(key, key), FAKE_BYTES, CachedClassEntry.EntryType.WEAVED);
+ }
+
+ public void testGetKeys() throws Exception {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ backing.put(createTestEntry("apple"));
+ backing.put(createTestEntry("apply"));
+ backing.put(createTestEntry("orange"));
+ String[] matches = backing.getKeys("app.*");
+ assertEquals(2, matches.length);
+ matches = backing.getKeys("orange");
+ assertEquals(1, matches.length);
+ assertEquals("orange", matches[0]);
+ }
+
+ public void testPut() throws Exception {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ backing.put(new CachedClassEntry(fakeRef, FAKE_BYTES, CachedClassEntry.EntryType.WEAVED));
+ File cachedFile = new File(root, fakeRef.getKey());
+ assertTrue(cachedFile.exists());
+ assertTrue(cachedFile.isFile());
+ assertEquals(FAKE_BYTES.length, cachedFile.length());
+ }
+
+ private boolean indexEntryExists(String key, long expectedCRC) throws Exception {
+ long storedCRC = 0;
+ DefaultFileCacheBacking.IndexEntry[] index = DefaultFileCacheBacking.readIndex(new File(root, DefaultFileCacheBacking.INDEX_FILE));
+ if (index == null) {
+ throw new NullPointerException("No index at " + root.getAbsolutePath());
+ }
+ for (DefaultFileCacheBacking.IndexEntry ie : index) {
+ if (ie.key.equals(key)) {
+ storedCRC = ie.crc;
+ if (!ie.ignored) {
+ assertEquals(expectedCRC, storedCRC);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void testGet() throws Exception {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ assertNull(backing.get(fakeRef));
+ backing.put(new CachedClassEntry(fakeRef, FAKE_BYTES, CachedClassEntry.EntryType.WEAVED));
+ File cachedFile = new File(root, fakeRef.getKey());
+ assertTrue(cachedFile.isFile());
+ assertEquals(FAKE_BYTES.length, cachedFile.length());
+ CRC32 expectedCRC = new CRC32();
+ expectedCRC.update(FAKE_BYTES);
+ assertTrue(indexEntryExists(fakeRef.getKey(), expectedCRC.getValue()));
+ CachedClassEntry entry = backing.get(fakeRef);
+ assertEquals(FAKE_BYTES.length, entry.getBytes().length);
+
+ }
+
+ public void testRemove() throws Exception {
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ backing.put(new CachedClassEntry(fakeRef, FAKE_BYTES, CachedClassEntry.EntryType.WEAVED));
+ File cachedFile = new File(root, fakeRef.getKey());
+ assertTrue(cachedFile.exists());
+ assertTrue(cachedFile.isFile());
+ CRC32 expectedCRC = new CRC32();
+ expectedCRC.update(FAKE_BYTES);
+ assertTrue(indexEntryExists(fakeRef.getKey(), expectedCRC.getValue()));
+ backing.remove(fakeRef);
+ cachedFile = new File(root, fakeRef.getKey());
+ assertFalse("CacheFile Still exists!" + cachedFile.getAbsolutePath(), cachedFile.exists());
+ assertFalse(cachedFile.isFile());
+ assertFalse(indexEntryExists(fakeRef.getKey(), expectedCRC.getValue()));
+ }
+
+
+ public void testMultiFile() throws Exception {
+ CachedClassEntry entry;
+ File cachedFile;
+ CRC32 expectedCRC = new CRC32();
+ expectedCRC.update(FAKE_BYTES);
+ CacheBacking backing = DefaultFileCacheBacking.createBacking(root, resolver);
+ // add weaved
+ CachedClassReference wref = resolver.weavedKey(FAKE_CLASS + "WEAVED", FAKE_BYTES);
+ entry = new CachedClassEntry(wref, FAKE_BYTES, CachedClassEntry.EntryType.WEAVED);
+ backing.put(entry);
+ cachedFile = new File(root, wref.getKey());
+ assertTrue(cachedFile.exists());
+ assertTrue(cachedFile.isFile());
+ assertTrue(indexEntryExists(wref.getKey(), expectedCRC.getValue()));
+
+ // add generated
+ CachedClassReference gref = resolver.generatedKey(FAKE_CLASS + "GENERATED");
+ entry = new CachedClassEntry(gref, FAKE_BYTES, CachedClassEntry.EntryType.GENERATED);
+ backing.put(entry);
+ cachedFile = new File(root, gref.getKey());
+ assertTrue(cachedFile.exists());
+ assertTrue(cachedFile.isFile());
+ assertTrue(indexEntryExists(gref.getKey(), expectedCRC.getValue()));
+
+ // add ignored
+ CachedClassReference iref = resolver.generatedKey(FAKE_CLASS + "IGNORED");
+ entry = new CachedClassEntry(iref, FAKE_BYTES, CachedClassEntry.EntryType.IGNORED);
+ backing.put(entry);
+ cachedFile = new File(root, iref.getKey());
+ assertFalse(cachedFile.exists());
+ assertTrue(indexEntryExists(iref.getKey(), expectedCRC.getValue()));
+
+ backing.remove(wref);
+ backing.remove(gref);
+ backing.remove(iref);
+ }
+
+}
diff --git a/weaver/testsrc/org/aspectj/weaver/tools/cache/WeavedClassCacheTest.java b/weaver/testsrc/org/aspectj/weaver/tools/cache/WeavedClassCacheTest.java
new file mode 100644
index 000000000..84406b558
--- /dev/null
+++ b/weaver/testsrc/org/aspectj/weaver/tools/cache/WeavedClassCacheTest.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * John Kew (vmware) initial implementation
+ *******************************************************************************/
+
+package org.aspectj.weaver.tools.cache;
+
+import junit.framework.TestCase;
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.weaver.tools.GeneratedClassHandler;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ */
+public class WeavedClassCacheTest extends TestCase {
+ String FAKE_CLASS = "com.example.foo.Bar";
+ byte[] FAKE_BYTES = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ public class MemoryCacheBacking implements CacheBacking {
+ HashMap<String, CachedClassEntry> cache = new HashMap<String, CachedClassEntry>();
+
+ public String[] getKeys(String regex) {
+ Set<String> keys = cache.keySet();
+ List<String> matches = new LinkedList<String>();
+ for (String key : keys) {
+ if (key.matches(regex)) {
+ matches.add(key);
+ }
+ }
+ return matches.toArray(new String[0]);
+ }
+
+ public void remove(CachedClassReference ref) {
+ cache.remove(ref.getKey());
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ public CachedClassEntry get(CachedClassReference ref) {
+ return cache.get(ref.getKey());
+ }
+
+ public void put(CachedClassEntry entry) {
+ cache.put(entry.getKey(), entry);
+ }
+ }
+
+ MemoryCacheBacking memoryBacking = new MemoryCacheBacking();
+
+ IMessageHandler messageHandler = new IMessageHandler() {
+ public boolean handleMessage(IMessage message) throws AbortException {
+ return true;
+ }
+
+ public boolean isIgnoring(IMessage.Kind kind) {
+ return true;
+ }
+
+ public void dontIgnore(IMessage.Kind kind) {
+ }
+
+ public void ignore(IMessage.Kind kind) {
+ }
+ };
+
+ public class TestGeneratedClassHandler implements GeneratedClassHandler {
+ public int accepts = 0;
+ public List<String> classesISaw = new LinkedList<String>();
+
+ public void acceptClass(String name, byte[] bytes) {
+ accepts++;
+ classesISaw.add(name);
+ }
+ }
+
+ TestGeneratedClassHandler generatedClassHandler = new TestGeneratedClassHandler();
+
+ CacheKeyResolver resolver = new DefaultCacheKeyResolver();
+
+ private WeavedClassCache createCache() throws Exception {
+ return new WeavedClassCache(generatedClassHandler, messageHandler, "test", memoryBacking, resolver);
+ }
+
+ private void reset() throws Exception {
+ memoryBacking.cache.clear();
+ generatedClassHandler.accepts = 0;
+ generatedClassHandler.classesISaw.clear();
+ }
+
+ public void testGetCachingClassHandler() throws Exception {
+ WeavedClassCache cache = createCache();
+ GeneratedClassHandler newHandle = cache.getCachingClassHandler();
+ assertTrue(generatedClassHandler != newHandle);
+ assertTrue(newHandle instanceof GeneratedCachedClassHandler);
+ }
+
+
+ public void testExistingGeneratedClassesPassedThroughHandler() throws Exception {
+ String classA = "com.generated.A";
+ String classB = "com.generated.B";
+ reset();
+ memoryBacking.put(new CachedClassEntry(resolver.generatedKey(classA), FAKE_BYTES, CachedClassEntry.EntryType.GENERATED));
+ memoryBacking.put(new CachedClassEntry(resolver.generatedKey(classB), FAKE_BYTES, CachedClassEntry.EntryType.GENERATED));
+ createCache();
+ assertEquals(2, generatedClassHandler.accepts);
+ for (String cName : generatedClassHandler.classesISaw) {
+ assertTrue("Got: " + cName, cName.equals(classA) || cName.equals(classB));
+ }
+ }
+
+ public void testCache() throws Exception {
+ reset();
+ WeavedClassCache cache = createCache();
+ CacheStatistics stats = cache.getStats();
+ CachedClassReference ref = cache.createCacheKey(FAKE_CLASS, FAKE_BYTES);
+ assertNull(cache.get(ref));
+ cache.put(ref, FAKE_BYTES);
+ assertNotNull(cache.get(ref));
+
+ assertEquals(new String(FAKE_BYTES), new String(cache.get(ref).getBytes()));
+
+ assertEquals(1, cache.getWeavedClasses().length);
+ assertEquals(ref.getKey(), cache.getWeavedClasses()[0].getKey());
+
+ ref = cache.createGeneratedCacheKey(FAKE_CLASS);
+ assertNull(cache.get(ref));
+ cache.put(ref, FAKE_BYTES);
+ assertNotNull(cache.get(ref));
+ assertEquals(new String(FAKE_BYTES), new String(cache.get(ref).getBytes()));
+
+ assertEquals(1, cache.getGeneratedClasses().length);
+ assertEquals(ref.getKey(), cache.getGeneratedClasses()[0].getKey());
+
+ assertEquals(4, stats.getHits());
+ assertEquals(2, stats.getMisses());
+
+
+ }
+
+ public void testRemove() throws Exception {
+ reset();
+ WeavedClassCache cache = createCache();
+ CachedClassReference ref = cache.createCacheKey(FAKE_CLASS, FAKE_BYTES);
+ assertNull(cache.get(ref));
+ cache.put(ref, FAKE_BYTES);
+ assertNotNull(cache.get(ref));
+ cache.remove(ref);
+ assertNull(cache.get(ref));
+ }
+
+}