From e07c4112d8a6161f110a3752158abcc0e1d290b2 Mon Sep 17 00:00:00 2001 From: jkew Date: Wed, 22 Feb 2012 16:19:51 -0800 Subject: [PATCH] AspectJ Caching w/ CRC Index File Signed-off-by: Andy Clement --- .../loadtime/ClassLoaderWeavingAdaptor.java | 42 ++- .../aspectj/weaver/tools/WeavingAdaptor.java | 89 ++++- .../weaver/tools/cache/CacheBacking.java | 58 ++++ .../weaver/tools/cache/CacheFactory.java | 25 ++ .../weaver/tools/cache/CacheKeyResolver.java | 88 +++++ .../weaver/tools/cache/CacheStatistics.java | 106 ++++++ .../weaver/tools/cache/CachedClassEntry.java | 57 +++ .../tools/cache/CachedClassReference.java | 57 +++ .../tools/cache/DefaultCacheFactory.java | 26 ++ .../tools/cache/DefaultCacheKeyResolver.java | 120 +++++++ .../tools/cache/DefaultFileCacheBacking.java | 309 +++++++++++++++++ .../cache/GeneratedCachedClassHandler.java | 38 ++ .../weaver/tools/cache/WeavedClassCache.java | 327 ++++++++++++++++++ .../aspectj/weaver/BcweaverModuleTests.java | 2 + .../weaver/tools/cache/CacheTests.java | 28 ++ .../cache/DefaultCacheKeyResolverTest.java | 92 +++++ .../cache/DefaultFileCacheBackingTest.java | 171 +++++++++ .../tools/cache/WeavedClassCacheTest.java | 168 +++++++++ 18 files changed, 1768 insertions(+), 35 deletions(-) create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CacheBacking.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CacheFactory.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CacheKeyResolver.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CacheStatistics.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CachedClassEntry.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/CachedClassReference.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheFactory.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolver.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/DefaultFileCacheBacking.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/GeneratedCachedClassHandler.java create mode 100644 weaver/src/org/aspectj/weaver/tools/cache/WeavedClassCache.java create mode 100644 weaver/testsrc/org/aspectj/weaver/tools/cache/CacheTests.java create mode 100644 weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultCacheKeyResolverTest.java create mode 100644 weaver/testsrc/org/aspectj/weaver/tools/cache/DefaultFileCacheBackingTest.java create mode 100644 weaver/testsrc/org/aspectj/weaver/tools/cache/WeavedClassCacheTest.java 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()) { @@ -202,10 +194,26 @@ public class ClassLoaderWeavingAdaptor extends WeavingAdaptor { } } + /** + * Get the list of all aspects from the defintion list + * @param definitions + * @return + */ + List getAspectClassNames(List definitions) { + List aspects = new LinkedList(); + for (Iterator it = definitions.iterator(); it.hasNext(); ) { + Definition def = it.next(); + List 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 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 ClassLoader */ public WeavingAdaptor(WeavingClassLoader loader) { // System.err.println("? WeavingAdaptor.(" + 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 GeneratedClassHandler, a full search path for resolving classes + * Construct a WeavingAdaptor with a reference to a GeneratedClassHandler, 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.()"); generatedClassHandler = handler; - init(FileUtil.makeClasspath(classURLs), FileUtil.makeClasspath(aspectURLs)); + init(null, FileUtil.makeClasspath(classURLs), FileUtil.makeClasspath(aspectURLs)); } - private List getFullClassPath(ClassLoader loader) { + protected List getFullClassPath(ClassLoader loader) { List list = new LinkedList(); for (; loader != null; loader = loader.getParent()) { if (loader instanceof URLClassLoader) { @@ -163,7 +158,13 @@ public class WeavingAdaptor implements IMessageContext { } } - private void init(List classPath, List 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 classPath, List 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 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. + *

+ * 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. + *

+ * 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: + *

+	 *    className == keyToClass(generatedKey(className)) holds
+	 * and
+	 *    generatedKey(className).matches(getGeneratedRegex()) == true
+	 * 
+ * + * @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 + *
+	 *    className == keyToClass(weavedKey(className, various_bytes)) holds
+	 * and
+	 *    weavedKey(className, various_bytes).matches(getWeavedRegex()) == true
+	 * 
+ * + * @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 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. + *

+ * 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. + *

+ * This implementation creates classloader scopes of the form:
+ * "ExampleClassLoaderName.[crc hash]" + *

+ * And weaved class keys of the form:
+ * "com.foo.BarClassName.[bytes len][crc].weaved" + *

+ * And generated class keys of the form:
+ * "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 aspects) { + String name = cl != null ? cl.getClass().getSimpleName() : "unknown"; + + List hashableStrings = new LinkedList(); + 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 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. + *

+ * Enabled with the system property, "aj.weaving.cache.dir" + * If this system property is not set, no caching will be + * performed. + *

+ * 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. + *

+ * 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 index = new HashMap(); + + 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). + *

+ * To enable the default configuration two system properties must be set: + *

+ *    "-Daj.weaving.cache.enabled=true"
+ *    "-Daj.weaving.cache.dir=/some/directory"
+ * 
+ *

+ * 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. + *

+ * {@link CacheBacking}
+ * 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. + *

+ * {@link CacheKeyResolver}
+ * 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. + *

+ * 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: + *

+ *    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.
+ * 
+ */ +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 cacheRegistry = new LinkedList(); + + 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 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 getCaches() { + synchronized (cacheRegistry) { + return new LinkedList(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 entries = new LinkedList(); + 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.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 cache = new HashMap(); + + public String[] getKeys(String regex) { + Set keys = cache.keySet(); + List matches = new LinkedList(); + 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 classesISaw = new LinkedList(); + + 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)); + } + +} -- 2.39.5