]> source.dussan.org Git - aspectj.git/commitdiff
some updates to ClassLoaderRepository - tested by RontimeWeaving
authoraclement <aclement>
Mon, 21 Aug 2006 15:23:58 +0000 (15:23 +0000)
committeraclement <aclement>
Mon, 21 Aug 2006 15:23:58 +0000 (15:23 +0000)
bcel-builder/src/org/aspectj/apache/bcel/util/ClassLoaderRepository.java
bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/ClassloaderRepositoryTest.java
lib/bcel/bcel-src.zip
lib/bcel/bcel.jar
weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java
weaver/src/org/aspectj/weaver/bcel/BcelWorld.java
weaver/src/org/aspectj/weaver/ltw/LTWWorld.java
weaver/testsrc/org/aspectj/weaver/bcel/ClassLoaderRepositoryTests.java

index 6c30dade8bc70f4f53a5eb1f00967dac39eeb696..2f732518694b6ab2bb5988d8bb54e2a42f48acde 100644 (file)
@@ -57,11 +57,15 @@ package org.aspectj.apache.bcel.util;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.AbstractMap;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.WeakHashMap;
 
 import org.aspectj.apache.bcel.classfile.ClassParser;
@@ -76,32 +80,35 @@ import org.aspectj.apache.bcel.classfile.JavaClass;
  *
  * @see org.aspectj.apache.bcel.Repository
  *
- * @version $Id: ClassLoaderRepository.java,v 1.7 2006/08/18 14:51:00 acolyer Exp $
+ * @version $Id: ClassLoaderRepository.java,v 1.8 2006/08/21 15:23:58 aclement Exp $
  * @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
  * @author David Dixon-Peugh
  */
 public class ClassLoaderRepository implements Repository {
   private static java.lang.ClassLoader bootClassLoader = null;
   private java.lang.ClassLoader loader;
-  private WeakHashMap /*<String classname,JavaClass>*/loadedClassesLocalCache = new WeakHashMap(); 
-  private static Map /*<URL,JavaClass>*/loadedUrlsSharedCache = new HashMap(); 
-  public static boolean useSharedCache = true;
   
-  private static long timeManipulatingURLs = 0L; 
-  private static long timeSpentLoading     = 0L;
-  private static int  classesLoadedCount = 0;
+  // Choice of cache...
+  private         WeakHashMap /*<URL,SoftRef(JavaClass)>*/localCache = new WeakHashMap(); 
+  private static SoftHashMap /*<URL,JavaClass>*/sharedCache = new SoftHashMap(Collections.synchronizedMap(new HashMap()));
+  
+  // For fast translation of the classname *intentionally not static*
+  private SoftHashMap /*<String,URL>*/ nameMap = new SoftHashMap(new HashMap(), false);
+  
+  public static boolean useSharedCache = 
+         System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true");
+  
   private static int  cacheHitsShared    = 0;
   private static int  missSharedEvicted  = 0; // Misses in shared cache access due to reference GC
-  private static int  misses             = 0;
+  private long timeManipulatingURLs = 0L; 
+  private long timeSpentLoading     = 0L;
+  private int  classesLoadedCount = 0;
+  private int  misses             = 0;
   private int  cacheHitsLocal     = 0;
   private int  missLocalEvicted   = 0; // Misses in local cache access due to reference GC
 
-  static {
-    useSharedCache = System.getProperty("org.aspectj.apache.bcel.useSharedCache","true").equalsIgnoreCase("true");
-  }
-  
   public ClassLoaderRepository( java.lang.ClassLoader loader ) {
-    this.loader = (loader != null) ? loader : getBootClassLoader();
+      this.loader = (loader != null) ? loader : getBootClassLoader();
   }
   
   private static synchronized java.lang.ClassLoader getBootClassLoader() {
@@ -110,43 +117,113 @@ public class ClassLoaderRepository implements Repository {
          }
          return bootClassLoader;
   }
+  
+  // Can track back to its key
+  public static class SoftHashMap extends AbstractMap {
+         private Map map;
+         boolean recordMiss = true; // only interested in recording miss stats sometimes
+         private ReferenceQueue rq = new ReferenceQueue(); 
+         
+      public SoftHashMap(Map map) { this.map = map; }
+         public SoftHashMap() { this(new HashMap()); }
+         public SoftHashMap(Map map, boolean b) { this(map); this.recordMiss=b;}
+       
+         class SpecialValue extends SoftReference {
+                 private final Object key;
+                 SpecialValue(Object k,Object v) {
+                   super(v,rq);
+                   this.key = k;
+                 }
+         }  
+
+         private void processQueue() {
+               SpecialValue sv = null;
+               while ((sv = (SpecialValue)rq.poll())!=null) {
+                       map.remove(sv.key);
+               }
+         }
+       
+         public Object get(Object key) {
+               SpecialValue value = (SpecialValue)map.get(key);
+               if (value==null) return null;
+               if (value.get()==null) {
+                       // it got GC'd
+                       map.remove(value.key);
+                       if (recordMiss) missSharedEvicted++;
+                       return null;
+               } else {
+                       return value.get();
+               }
+         }
+
+         public Object put(Object k, Object v) {
+               processQueue();
+               return map.put(k, new SpecialValue(k,v));
+         }
+
+         public Set entrySet() {
+               return map.entrySet();
+         }
+       
+         public void clear() {
+               processQueue();
+               map.clear();
+         }
+       
+         public int size() {
+               processQueue();
+               return map.size();
+         }
+       
+         public Object remove(Object k) {
+               processQueue();
+               SpecialValue value = (SpecialValue)map.remove(k);
+               if (value==null) return null;
+               if (value.get()!=null) {
+                       return value.get();
+               }
+               return null;
+         }
+  }
 
   /**
    * Store a new JavaClass into this repository as a soft reference and return the reference
    */
-  private Reference storeClassAsReference( JavaClass clazz ) {
-       Reference ref = new SoftReference(clazz);
-    loadedClassesLocalCache.put( clazz.getClassName(), ref);                  
-    clazz.setRepository( this );
-    return ref;
+  private void storeClassAsReference(URL url, JavaClass clazz ) {
+       if (useSharedCache) {
+               clazz.setRepository(null); // can't risk setting repository, we'll get in a pickle!
+               sharedCache.put(url, clazz);
+       } else {
+           clazz.setRepository(this);
+               localCache.put(url, new SoftReference(clazz));
+       }
   }
   
-  /**
-   * Store a reference in the shared cache
-   */
-  private void storeReferenceShared(URL url, Reference ref) {
-         if (useSharedCache) loadedUrlsSharedCache.put(url, ref);
-  }
-
   /**
    * Store a new JavaClass into this Repository.
    */
   public void storeClass( JavaClass clazz ) {
-         storeClassAsReference(clazz);
+         storeClassAsReference(toURL(clazz.getClassName()),clazz);
   }
 
   /**
    * Remove class from repository
    */
   public void removeClass(JavaClass clazz) {
-    loadedClassesLocalCache.remove(clazz.getClassName());
+    if (useSharedCache) sharedCache.remove(toURL(clazz.getClassName()));
+    else                localCache.remove(toURL(clazz.getClassName()));
   }
 
   /**
    * Find an already defined JavaClass in the local cache.
    */
   public JavaClass findClass( String className ) {
-    Object o = loadedClassesLocalCache.get( className );
+         if (useSharedCache) return findClassShared(toURL(className));
+         else                return findClassLocal(toURL(className));
+  }
+  
+  private JavaClass findClassLocal( URL url ) {
+    Object o = localCache.get(url);
     if (o != null) {
        o = ((Reference)o).get();
        if (o != null) {
@@ -162,55 +239,56 @@ public class ClassLoaderRepository implements Repository {
    * Find an already defined JavaClass in the shared cache.
    */
   private JavaClass findClassShared(URL url) {
-         if (!useSharedCache) return null;
-         Object o = (Reference)loadedUrlsSharedCache.get(url);
-         if (o != null) {
-               o = ((Reference)o).get();
-               if (o != null) {
-                       return (JavaClass)o; 
-               } else { 
-                       missSharedEvicted++; 
-               }
-         }
-         return null;
+         return (JavaClass)sharedCache.get(url);
   }
 
+  private URL toURL(String className) {
+         URL url = (URL)nameMap.get(className);
+         if (url==null) {
+                 String classFile = className.replace('.', '/');
+             url = loader.getResource( classFile + ".class" );
+             nameMap.put(className, url);
+         }
+      return url;
+  }
   
   /**
    * Lookup a JavaClass object from the Class Name provided.
    */
   public JavaClass loadClass( String className ) throws ClassNotFoundException {
-    String classFile = className.replace('.', '/');
-
-    // Check the local cache
-    JavaClass clazz = findClass(className);
-    if (clazz != null) { cacheHitsLocal++; return clazz; }
+    
+       // translate to a URL
+       long time = System.currentTimeMillis();
+    java.net.URL url = toURL(className);
+       timeManipulatingURLs += (System.currentTimeMillis() - time);
+       if (url==null) throw new ClassNotFoundException(className + " not found.");
+    
+       JavaClass clazz = null;
 
-    try {
-       // Work out the URL
-       long time = System.currentTimeMillis();
-       java.net.URL url = (useSharedCache?loader.getResource( classFile + ".class" ):null);
-       if (useSharedCache && url==null) throw new ClassNotFoundException(className + " not found.");
-               InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" ));
-               timeManipulatingURLs += (System.currentTimeMillis() - time);
-               
-               // Check the shared cache
+       // Look in the appropriate cache
+       if (useSharedCache) {
                clazz = findClassShared(url);
-               if (clazz != null) { cacheHitsShared++; timeSpentLoading+=(System.currentTimeMillis() - time); return clazz; } 
+               if (clazz != null) { cacheHitsShared++; return clazz; }         
+       } else {
+           clazz = findClassLocal(url);
+           if (clazz != null) { cacheHitsLocal++; return clazz; }
+       }
+    
+       // Didn't find it in either cache
+       misses++;
 
-               // Didn't find it in either cache
-               misses++;
-           
-        if (is == null) { // TODO move this up?
-         throw new ClassNotFoundException(className + " not found.");
-        }
-
-        ClassParser parser = new ClassParser( is, className );
+    try {      
+       // Load it
+           String classFile = className.replace('.', '/');
+               InputStream is = (useSharedCache?url.openStream():loader.getResourceAsStream( classFile + ".class" ));
+           if (is == null) { 
+                 throw new ClassNotFoundException(className + " not found.");
+           }
+               ClassParser parser = new ClassParser( is, className );
         clazz = parser.parse();
            
-        // Store it in both caches
-        Reference ref = storeClassAsReference( clazz );
-        storeReferenceShared(url,ref);
+        // Cache it
+        storeClassAsReference(url, clazz );
 
         timeSpentLoading += (System.currentTimeMillis() - time);
            classesLoadedCount++;
@@ -221,27 +299,42 @@ public class ClassLoaderRepository implements Repository {
   }
   
 
-/**
+  /**
    * Produce a report on cache usage.
    */
-  public String reportAllStatistics() {
+  public String report() {
          StringBuffer sb = new StringBuffer();
          sb.append("BCEL repository report.");
-         if (!useSharedCache) sb.append(" (Shared cache deactivated)");
+         if (useSharedCache) sb.append(" (shared cache)");
+         else                sb.append(" (local cache)");
          sb.append(" Total time spent loading: "+timeSpentLoading+"ms.");
-         sb.append(" Time manipulating URLs: "+timeManipulatingURLs+"ms.");
+         sb.append(" Time spent manipulating URLs: "+timeManipulatingURLs+"ms.");
          sb.append(" Classes loaded: "+classesLoadedCount+".");
-         if (useSharedCache) sb.append(" URL cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+").");
-         sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+").");
+         if (useSharedCache) {
+                 sb.append(" Shared cache size: "+sharedCache.size());
+             sb.append(" Shared cache (hits/missDueToEviction): ("+cacheHitsShared+"/"+missSharedEvicted+").");
+         } else {
+                 sb.append(" Local cache size: "+localCache.size());
+                 sb.append(" Local cache (hits/missDueToEviction): ("+cacheHitsLocal+"/"+missLocalEvicted+").");
+         }
          return sb.toString();
   }
   
-  public int reportLocalCacheHits() {
-         return cacheHitsLocal;
-  }
-
-  public static int reportSharedCacheHits() {
-         return cacheHitsShared;
+  /**
+   * Returns an array of the stats, for testing, the order is fixed:
+   * 0=time spent loading (static)
+   * 1=time spent manipulating URLs (static)
+   * 2=classes loaded (static)
+   * 3=cache hits shared (static)
+   * 4=misses in shared due to eviction (static)
+   * 5=cache hits local
+   * 6=misses in local due to eviction
+   * 7=shared cache size
+   */
+  public long[] reportStats() {
+         return new long[]{timeSpentLoading,timeManipulatingURLs,classesLoadedCount,
+                                    cacheHitsShared,missSharedEvicted,cacheHitsLocal,missLocalEvicted,
+                                    sharedCache.size()};
   }
   
   /**
@@ -257,7 +350,6 @@ public class ClassLoaderRepository implements Repository {
          missLocalEvicted  = 0; 
          misses = 0;
          clear();
-         clearShared();
   }
   
   
@@ -267,12 +359,10 @@ public class ClassLoaderRepository implements Repository {
 
   /** Clear all entries from the local cache */
   public void clear() {
-      loadedClassesLocalCache.clear();
-  }
-
-  /** Clear all entries from the shared cache */
-  public static void clearShared() {
-         loadedUrlsSharedCache.clear();
+         if (useSharedCache) sharedCache.clear();
+         else                localCache.clear();
   }
+  
 }
 
+
index d9e0d6d583ac0194b17b945caaf6095b7f582067..7202be3faf835bab6bc28fc16317dd2fa0d20b3e 100644 (file)
@@ -26,20 +26,33 @@ public class ClassloaderRepositoryTest extends TestCase {
 
        // Retrieve string 5 times from same repository, 4 hits should be from local cache
        public void testLocalCacheWorks() throws ClassNotFoundException {               
+               ClassLoaderRepository.useSharedCache=false;
                JavaClass jc = rep1.loadClass("java.lang.String");
                jc = rep1.loadClass("java.lang.String");
                jc = rep1.loadClass("java.lang.String");
                jc = rep1.loadClass("java.lang.String");
                jc = rep1.loadClass("java.lang.String");
-               assertTrue("Should have used local cache 4 times: "+rep1.reportLocalCacheHits(),rep1.reportLocalCacheHits()==4);
+               assertTrue("Should have used local cache 4 times: "+reportLocalCacheHits(rep1),reportLocalCacheHits(rep1)==4);
+       }
+
+       // Retrieve string 5 times from same repository, 4 hits should be from local cache
+       public void testSharedCacheWorksOnOne() throws ClassNotFoundException {         
+               ClassLoaderRepository.useSharedCache=true;
+               JavaClass jc = rep1.loadClass("java.lang.String");
+               jc = rep1.loadClass("java.lang.String");
+               jc = rep1.loadClass("java.lang.String");
+               jc = rep1.loadClass("java.lang.String");
+               jc = rep1.loadClass("java.lang.String");
+               assertTrue("Should have used local cache 4 times: "+reportSharedCacheHits(rep1),reportSharedCacheHits(rep1)==4);
        }
 
        // Retrieve String through one repository then load again through another, should be shared cache hit
        public void testSharedCacheWorks() throws ClassNotFoundException {              
+               ClassLoaderRepository.useSharedCache=true;
                JavaClass jc = rep1.loadClass("java.lang.String");
                jc = rep2.loadClass("java.lang.String");
-               assertTrue("Should have retrieved String from shared cache: "+ClassLoaderRepository.reportSharedCacheHits(),
-                               ClassLoaderRepository.reportSharedCacheHits()==1);
+               assertTrue("Should have retrieved String from shared cache: "+reportSharedCacheHits(rep1),
+                               reportSharedCacheHits(rep1)==1);
        }
        
        // Shared cache OFF, shouldn't get a shared cache hit
@@ -49,8 +62,8 @@ public class ClassloaderRepositoryTest extends TestCase {
                        JavaClass jc = rep1.loadClass("java.lang.String");
                        jc = rep2.loadClass("java.lang.String");
                        assertTrue("Should not have retrieved String from shared cache: "+
-                                       ClassLoaderRepository.reportSharedCacheHits(),
-                                   ClassLoaderRepository.reportSharedCacheHits()==0);
+                                       reportSharedCacheHits(rep1),
+                                   reportSharedCacheHits(rep1)==0);
                } finally {
                        ClassLoaderRepository.useSharedCache=true;
                }
@@ -58,10 +71,19 @@ public class ClassloaderRepositoryTest extends TestCase {
        
        public void tearDown() throws Exception {
                super.tearDown();
-               System.err.println("Rep1: "+rep1.reportAllStatistics());
-               System.err.println("Rep2: "+rep2.reportAllStatistics());
+               System.err.println("Rep1: "+rep1.reportStats());
+               System.err.println("Rep2: "+rep2.reportStats());
                rep1.reset();
                rep2.reset();
        }
-       
+
+       private long reportLocalCacheHits(ClassLoaderRepository rep) {
+               return rep.reportStats()[5];
+       }
+
+       private long reportSharedCacheHits(ClassLoaderRepository rep) {
+               return rep.reportStats()[3];
+       }
+
 }
+
index 1d7d84805e76678cfd398a566b81678847a70951..c1115a8864eb84e4666901e413f01aaebb771daa 100644 (file)
Binary files a/lib/bcel/bcel-src.zip and b/lib/bcel/bcel-src.zip differ
index 493ffe6a2a13e8251ca6094502c99d6c3f329d19..7df53e5401d10976b7234e7c576a2368e044eabb 100644 (file)
Binary files a/lib/bcel/bcel.jar and b/lib/bcel/bcel.jar differ
index 6a51fd484b45724acf48badfe239b0b25c40ef2d..38a56ef5b2bfa8fd71ff7ec1a86eef41ec6b8293 100644 (file)
@@ -187,7 +187,12 @@ public class BcelWeaver implements IWeaver {
                        WeaverStateInfo wsi = type.getWeaverState();            
                        if (wsi != null && wsi.isReweavable()) {
                            BcelObjectType classType = getClassType(type.getName());
-                               classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes())));
+                               JavaClass wovenJavaClass = classType.getJavaClass();
+                               JavaClass unwovenJavaClass = Utility.makeJavaClass(wovenJavaClass.getFileName(),
+                                 wsi.getUnwovenClassFileData(wovenJavaClass.getBytes()));
+                               world.storeClass(unwovenJavaClass);
+                               classType.setJavaClass(unwovenJavaClass);
+//                             classType.setJavaClass(Utility.makeJavaClass(classType.getJavaClass().getFileName(), wsi.getUnwovenClassFileData(classType.getJavaClass().getBytes())));
                        }
                        
             //TODO AV - happens to reach that a lot of time: for each type flagged reweavable X for each aspect in the weaverstate
index b0d5ce96ac8a81bcae599456436d6096617c9584..2e13c5d54edf60928c67e61824b7d1696a915e83 100644 (file)
@@ -81,7 +81,7 @@ import org.aspectj.weaver.patterns.SimpleScope;
 public class BcelWorld extends World implements Repository {
        private ClassPathManager classPath;
 
-    private Repository delegate;
+    protected Repository delegate;
     
     
        //private ClassPathManager aspectPath = null;
@@ -644,7 +644,7 @@ public class BcelWorld extends World implements Repository {
        }
 
        public void storeClass(JavaClass clazz) {
-               throw new RuntimeException("Not implemented");
+               // doesn't need to do anything
        }
 
        public void removeClass(JavaClass clazz) {
index ff6b7b727ceedec5f6d155db80b18de4c21dd6c0..a89dc6f7742dc72742112c830f4359d06fd62c78 100644 (file)
@@ -19,6 +19,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.aspectj.apache.bcel.classfile.JavaClass;
 import org.aspectj.bridge.IMessageHandler;
 import org.aspectj.util.LangUtil;
 import org.aspectj.weaver.ICrossReferenceHandler;
@@ -48,7 +49,6 @@ import org.aspectj.weaver.reflect.ReflectionWorld;
  */
 public class LTWWorld extends BcelWorld implements IReflectionWorld {
        
-
        private AnnotationFinder annotationFinder;
     private ClassLoader loader; // weavingContext?
     private IWeavingContext weavingContext;
@@ -244,5 +244,10 @@ public class LTWWorld extends BcelWorld implements IReflectionWorld {
                ((ReferenceType)ret).setDelegate(rtd);
                return ret;
        }
+
+       public void storeClass(JavaClass clazz) {
+               delegate.storeClass(clazz);
+       }
+
     
 }
index c60f43d574840afae2817fe24facff585d230ee7..a6f9b0000ba9aa50d5685cf74e9162a167cdd24e 100644 (file)
 package org.aspectj.weaver.bcel;
 
 import java.io.File;
-import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.reflect.Field;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Enumeration;
+import java.util.Map;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -27,43 +29,185 @@ import org.aspectj.apache.bcel.util.ClassLoaderRepository;
 
 /** NOT YET INCLUDED IN A FULL TEST RUN - WORK IN PROGRESS CHECKING CLASSLOADERREPOSITORY OPTIMIZATIONS */
 public class ClassLoaderRepositoryTests extends TestCase {
-
-        public void testRepositorySharing() throws  Exception {
-       ClassLoaderRepository.useSharedCache=false;
-       File f = new File("../lib/aspectj/lib/aspectjtools.jar");
+       private File f;
+       private ZipFile zf;
+       private Enumeration entries;
+       private Map map;
+       
+       public void setUp() throws Exception {
+       f = new File("../lib/aspectj/lib/aspectjtools.jar");
+               assertTrue("Couldn't find aspectjtools to test.  Tried: "+f.getAbsolutePath(),f.exists());
+               zf = new ZipFile(f);            
+               entries = zf.entries();
+//             ClassLoaderRepository.sharedCacheCompactFrequency = 16384;
+               map = getSharedMap();
+       }
+       
+       public void tearDown() {
+                new ClassLoaderRepository(null).reset();
+       }
+       
+       private ClassLoaderRepository setupRepository() throws Exception {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-               ClassLoader cl1 = new URLClassLoader(new URL[]{f.toURL()},cl);
-               ClassLoader cl2 = new URLClassLoader(new URL[]{f.toURL()},cl);
-               ClassLoaderRepository rep1 = new ClassLoaderRepository(cl1);
-               ClassLoaderRepository rep2 = new ClassLoaderRepository(cl2);
-               try {
-                       assertTrue("Couldnt find aspectjtools to test.  Tried: "+f.getAbsolutePath(),f.exists());
-                       ZipFile zf = new ZipFile(f);
-                       int i = 0;
-                       Enumeration entries = zf.entries();
-                       while (entries.hasMoreElements()) {
-                               ZipEntry zfe = (ZipEntry)entries.nextElement();
-                               String classfileName = zfe.getName();
-                               if (classfileName.endsWith(".class")) {
-                                       String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.');
-                                       
-                                       // twice by each
-                                       rep1.loadClass(clazzname);
-                                       rep1.loadClass(clazzname);
-                                       rep2.loadClass(clazzname);  
-                                       rep2.loadClass(clazzname);  
-                                       i++;
-                               }
+               ClassLoader res = new URLClassLoader(new URL[]{f.toURL()},cl);
+               ClassLoaderRepository rep = new ClassLoaderRepository(res);
+               return rep;
+       }
+       
+        private void compareTwoRepositories() throws Exception {
+               ClassLoaderRepository rep1 = setupRepository();
+               ClassLoaderRepository rep2 = setupRepository();
+               int i = 0;
+               while (entries.hasMoreElements()) {
+                       ZipEntry zfe = (ZipEntry)entries.nextElement();
+                       String classfileName = zfe.getName();
+                       if (classfileName.endsWith(".class")) {
+                               String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.');
+                               
+                               // twice by each
+                               rep1.loadClass(clazzname);
+                               rep1.loadClass(clazzname);
+                               rep2.loadClass(clazzname);  
+                               rep2.loadClass(clazzname);  
+                               i++;
+                       }
+               }
+               System.err.println("Successfully compared "+i+" entries!!");
+               System.err.println(rep1.report());
+               System.err.println(rep2.report());
+    }
+
+        private void loadOnce() throws Exception {
+               ClassLoaderRepository rep = setupRepository();
+               while (entries.hasMoreElements()) {
+                       ZipEntry zfe = (ZipEntry) entries.nextElement();
+                       String classfileName = zfe.getName();
+                       if (classfileName.endsWith(".class")) {
+                               String clazzname = classfileName.substring(0,
+                                               classfileName.length() - 6).replace('/', '.');
+
+                               rep.loadClass(clazzname);
+                       }
+               }
+       }
+
+        public void testMultiThreaded() throws Throwable {              
+                ClassLoaderRepository.useSharedCache=true;
+//              ClassLoaderRepository.sharedCacheCompactFrequency = 200;
+                //loadOnce();
+                TestThread threads[] = new TestThread[6];
+                for (int i=0; i<threads.length; i++) {
+                        threads[i] = new TestThread((i%3)*1000);
+                        threads[i].start();
+                }
+                for (int i=0; i<threads.length; i++) {
+                        threads[i].join();
+                        if (threads[i].getFailure() != null) {
+                                throw threads[i].getFailure();
+                        }
+                }
+        }
+        
+        private class TestThread extends Thread {
+                public Throwable failure = null;
+                Enumeration entries;
+                
+                // ensure the threads are loading DIFFERENT shared classes at the same time...
+                public TestThread(int skip) {
+                       entries = zf.entries();
+                       for (int i=0; i<skip && entries.hasMoreElements(); i++) {
+                               entries.nextElement();
                        }
-                       System.err.println("Successfully compared "+i+" entries!!");
-               } catch (IOException e) {
-                       e.printStackTrace();
-                       fail(e.getMessage());
+                }
+                
+                public void run() {
+                        try {
+                               ClassLoaderRepository rep = setupRepository();
+                               int i = 0;
+                               while (entries.hasMoreElements()) {
+                                       ZipEntry zfe = (ZipEntry)entries.nextElement();
+                                       String classfileName = zfe.getName();
+                                       if (classfileName.endsWith(".class")) {
+                                               String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.');
+                                               rep.loadClass(clazzname);
+                                               rep.loadClass(clazzname);
+                                               i++;
+                                       }
+                               }                                
+                               System.err.println("Thread finished: "+rep.report());
+                        } catch (Throwable t) {
+                                failure = t;
+                        }
+               }
+               public Throwable getFailure() {
+                       return failure;
                }
-               System.err.println(rep1.reportAllStatistics());
-               System.err.println(rep2.reportAllStatistics());
+        }
+        
+        public void testNotSharedRepository() throws  Exception {
+       ClassLoaderRepository.useSharedCache=false;
+       compareTwoRepositories();
+        }
+                
+        public void testSharedUrlRepository() throws  Exception {
+               ClassLoaderRepository.useSharedCache=true;
+               compareTwoRepositories();
+//             ClassLoaderRepository.compactSharedCache();
     }
-    
         
+       public void testPurgeUrlRepository() throws  Exception {                
+               ClassLoaderRepository.useSharedCache = true;
+               ClassLoaderRepository rep = setupRepository();
+               Reference ref = null;
+               
+               while (ref==null && entries.hasMoreElements()) {
+                       ZipEntry zfe = (ZipEntry)entries.nextElement();
+                       String classfileName = zfe.getName();
+                       if (classfileName.endsWith(".class")) {
+                               String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.');
+                               
+                               rep.loadClass(clazzname);
+                               assertEquals("expected one entry in shared URL cache "+map.size(), 1, map.size());
+                               ref = (Reference)map.values().iterator().next();
+                               ref.clear();
+                               ref.enqueue();
+                               map.size();//force purge
+                       }
+               }
+               assertEquals("expected empty shared URL cache "+map.size(), 0, map.size());
+       }
+       
+       public void testAutoPurgeUrlRepository() throws  Exception {            
+               ClassLoaderRepository.useSharedCache = true;
+               assertEquals("expected empty shared URL cache "+map.size(), 0, map.size());
+               ClassLoaderRepository rep = setupRepository();
+               Reference ref = null;
+               int i = 0;
+               
+               while (i<3 && entries.hasMoreElements()) {
+                       ZipEntry zfe = (ZipEntry)entries.nextElement();
+                       String classfileName = zfe.getName();
+                       if (classfileName.endsWith(".class")) {
+                               String clazzname = classfileName.substring(0,classfileName.length()-6).replace('/','.');
+                               
+                               rep.loadClass(clazzname);
+                               ref = (Reference)map.values().iterator().next();
+                               ref.clear();
+                               ref.enqueue();
+                               i++;
+                       }
+               }
+               assertTrue("expected smaller shared URL cache "+map.size(), map.size()<3);
+       }
+       
+       private Field getSharedMapField() throws Exception {
+               Field field = ClassLoaderRepository.class.getDeclaredField("sharedCache");
+               field.setAccessible(true);
+               return field;
+       }
        
+       private Map getSharedMap() throws Exception {
+               return (Map)getSharedMapField() .get(null);
+       }
 }
+