]> source.dussan.org Git - javassist.git/commitdiff
Move scoped classpool repository from jboss retro into javassist to have a common...
authorkkhan <kkhan@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Thu, 13 Jul 2006 09:43:16 +0000 (09:43 +0000)
committerkkhan <kkhan@30ef5769-5b8d-40dd-aea6-55b5d6557bb3>
Thu, 13 Jul 2006 09:43:16 +0000 (09:43 +0000)
git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@289 30ef5769-5b8d-40dd-aea6-55b5d6557bb3

src/main/META-INF/MANIFEST.MF
src/main/javassist/scopedpool/ScopedClassPool.java [new file with mode: 0644]
src/main/javassist/scopedpool/ScopedClassPoolFactory.java [new file with mode: 0644]
src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java [new file with mode: 0644]
src/main/javassist/scopedpool/ScopedClassPoolRepository.java [new file with mode: 0644]
src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java [new file with mode: 0644]
src/main/javassist/scopedpool/SoftValueHashMap.java [new file with mode: 0644]

index 8cebde7c6e63722242e9af19f2192ec6bc3de18c..3d71dea69e95f5f92f29de61e9ca91b670c69188 100644 (file)
@@ -2,7 +2,7 @@ Manifest-Version: 1.1
 Specification-Title: Javassist
 Created-By: Shigeru Chiba, Tokyo Institute of Technology
 Specification-Vendor: Shigeru Chiba, Tokyo Institute of Technology
-Specification-Version: 3.2
+Specification-Version: 3.3.0.snapshot
 Main-Class: javassist.CtClass
 
 Name: javassist/
diff --git a/src/main/javassist/scopedpool/ScopedClassPool.java b/src/main/javassist/scopedpool/ScopedClassPool.java
new file mode 100644 (file)
index 0000000..07c0c17
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package javassist.scopedpool;
+
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.Map;
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+/**
+ * A scoped class pool
+ * 
+ * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
+ * @author <a href="adrian@jboss.com">Adrian Brock</a>
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.1 $
+ */
+public class ScopedClassPool extends ClassPool
+{
+   protected ScopedClassPoolRepository repository;
+   protected WeakReference classLoader;
+   protected LoaderClassPath classPath;
+   protected SoftValueHashMap softcache = new SoftValueHashMap();
+
+   static 
+   {
+      ClassPool.doPruning = false;
+      ClassPool.releaseUnmodifiedClassFile = false;
+   }   
+   
+   /**
+    * Create a new ScopedClassPool.
+    * 
+    * @param cl the classloader
+    * @param src the original class pool
+    * @param repository the repository
+    */
+   protected ScopedClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository)
+   {
+      super(src);
+      this.repository = repository;
+      this.classLoader = new WeakReference(cl);
+      if (cl != null)
+      {
+         classPath = new LoaderClassPath(cl);
+         this.insertClassPath(classPath);
+      }
+      childFirstLookup = true;
+   }
+
+   /**
+    * Get the class loader
+    * 
+    * @return the class loader
+    */
+   public ClassLoader getClassLoader()
+   {
+      return getClassLoader0();
+   }
+
+   private ClassLoader getClassLoader0()
+   {
+      ClassLoader cl = (ClassLoader) classLoader.get();
+      if (cl == null)
+         throw new IllegalStateException("ClassLoader has been garbage collected");
+      return cl;
+   }
+   
+   /**
+    * Close the class pool
+    */
+   public void close()
+   {
+      this.removeClassPath(classPath);
+      classPath.close();
+      classes.clear();
+      softcache.clear();
+   }
+
+   /**
+    * Flush a class
+    * 
+    * @param classname the class to flush
+    */
+   public synchronized void flushClass(String classname)
+   {
+      classes.remove(classname);
+      softcache.remove(classname);
+   }
+
+   /**
+    * Soften a class
+    * 
+    * @param clazz the class
+    */
+   public synchronized void soften(CtClass clazz)
+   {
+      if (repository.isPrune()) clazz.prune();
+      classes.remove(clazz.getName());
+      softcache.put(clazz.getName(), clazz);
+   }
+
+   /**
+    * Whether the classloader is loader
+    * 
+    * @return false always
+    */
+   public boolean isUnloadedClassLoader()
+   {
+      return false;
+   }
+
+   /**
+    * Get the cached class
+    * 
+    * @param classname the class name
+    * @return the class
+    */
+   protected CtClass getCached(String classname)
+   {
+      CtClass clazz = getCachedLocally(classname);
+      if (clazz == null)
+      {
+         boolean isLocal = false; 
+         
+         ClassLoader dcl = getClassLoader0();
+         if (dcl != null)
+         {
+            final int lastIndex = classname.lastIndexOf('$');
+            String classResourceName = null;
+            if (lastIndex < 0)
+            {
+               classResourceName = classname.replaceAll("[\\.]", "/") + ".class";
+            }
+            else
+            {
+               classResourceName = classname.substring(0, lastIndex).replaceAll("[\\.]", "/") + classname.substring(lastIndex) + ".class";
+            }
+
+            isLocal = dcl.getResource(classResourceName) != null; 
+         }
+         
+         if (!isLocal)
+         {
+            Map registeredCLs = repository.getRegisteredCLs();
+            synchronized (registeredCLs)
+            {
+               Iterator it = registeredCLs.values().iterator();
+               while (it.hasNext())
+               {
+                  ScopedClassPool pool = (ScopedClassPool) it.next();
+                  if (pool.isUnloadedClassLoader())
+                  {
+                     repository.unregisterClassLoader(pool.getClassLoader());
+                     continue;
+                  }
+   
+                  clazz = pool.getCachedLocally(classname);
+                  if (clazz != null)
+                  {
+                     return clazz;
+                  }
+               }
+            }
+         }
+      }
+      // *NOTE* NEED TO TEST WHEN SUPERCLASS IS IN ANOTHER UCL!!!!!!
+      return clazz;
+   }
+   
+   /**
+    * Cache a class
+    * 
+    * @param classname the class name
+    * @param c the ctClass
+    * @param dynamic whether the class is dynamically generated
+    */
+   protected void cacheCtClass(String classname, CtClass c, boolean dynamic)
+   {
+      if (dynamic)
+      {
+         super.cacheCtClass(classname, c, dynamic);
+      }
+      else
+      {
+         if (repository.isPrune()) c.prune();
+         softcache.put(classname, c);
+      }
+   }
+   
+   /**
+    * Lock a class into the cache
+    * 
+    * @param c the class
+    */
+   public void lockInCache(CtClass c)
+   {
+      super.cacheCtClass(c.getName(), c, false);
+   }
+
+   /**
+    * Whether the class is cached in this pooled
+    * 
+    * @param classname the class name
+    * @return the cached class
+    */
+   protected CtClass getCachedLocally(String classname)
+   {
+      CtClass cached = (CtClass) classes.get(classname);
+      if (cached != null) return cached;
+      synchronized (softcache)
+      {
+         return (CtClass) softcache.get(classname);
+      }
+   }
+
+   /**
+    * Get any local copy of the class
+    * 
+    * @param classname the class name
+    * @return the class
+    * @throws NotFoundException when the class is not found
+    */
+   public synchronized CtClass getLocally(String classname) throws NotFoundException
+   {
+      softcache.remove(classname);
+      CtClass clazz = (CtClass) classes.get(classname);
+      if (clazz == null)
+      {
+         clazz = createCtClass(classname, true);
+         if (clazz == null) throw new NotFoundException(classname);
+         super.cacheCtClass(classname, clazz, false);
+      }
+
+      return clazz;
+   }
+
+   /**
+    * Convert a javassist class to a java class
+    * 
+    * @param ct the javassist class
+    * @param loader the loader
+    * @throws CannotCompileException for any error
+    */
+   public Class toClass(CtClass ct, ClassLoader loader) throws CannotCompileException
+   {
+      //We need to pass up the classloader stored in this pool, as the default implementation uses the Thread context cl.
+      //In the case of JSP's in Tomcat, org.apache.jasper.servlet.JasperLoader will be stored here, while it's parent
+      //org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader is used as the Thread context cl. The invocation class needs to
+      // be generated in the JasperLoader classloader since in the case of method invocations, the package name will be
+      //the same as for the class generated from the jsp, i.e. org.apache.jsp. For classes belonging to org.apache.jsp,
+      //JasperLoader does NOT delegate to its parent if it cannot find them.
+      lockInCache(ct);
+      return super.toClass(ct, getClassLoader0());
+   }
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactory.java b/src/main/javassist/scopedpool/ScopedClassPoolFactory.java
new file mode 100644 (file)
index 0000000..a91412e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/ 
+package javassist.scopedpool;
+
+import javassist.ClassPool;
+
+/**
+ * 
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.1 $
+ */
+public interface ScopedClassPoolFactory
+{
+   ScopedClassPool create(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository);
+   
+   ScopedClassPool create(ClassPool src, ScopedClassPoolRepository repository);
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java
new file mode 100644 (file)
index 0000000..d6f26eb
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/ 
+package javassist.scopedpool;
+
+import javassist.ClassPool;
+
+
+/**
+ * 
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.1 $
+ */
+public class ScopedClassPoolFactoryImpl implements ScopedClassPoolFactory
+{
+   public ScopedClassPool create(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository)
+   {
+      return new ScopedClassPool(cl, src, repository);
+   }
+
+   public ScopedClassPool create(ClassPool src, ScopedClassPoolRepository repository)
+   {
+      return new ScopedClassPool(null, src, repository);
+   }   
+}
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepository.java b/src/main/javassist/scopedpool/ScopedClassPoolRepository.java
new file mode 100644 (file)
index 0000000..b4d55c0
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package javassist.scopedpool;
+
+import java.util.Map;
+
+import javassist.ClassPool;
+
+/**
+ * 
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.1 $
+ */
+public interface ScopedClassPoolRepository
+{
+   void setClassPoolFactory(ScopedClassPoolFactory factory);
+   
+   ScopedClassPoolFactory getClassPoolFactory();
+
+   /**
+    * Get the prune.
+    * 
+    * @return the prune.
+    */
+   boolean isPrune();
+
+   /**
+    * Set the prune.
+    * 
+    * @param prune the prune.
+    */
+   void setPrune(boolean prune);
+   
+   /**
+    * Create a scoped classpool
+    * 
+    * @param cl the classloader
+    * @param src the original classpool
+    * @return the classpool
+    */
+   ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src);
+
+   /**
+    * Finds a scoped classpool registered under the passed in classloader
+    * @param the classloader
+    * @return the classpool
+    */
+   ClassPool findClassPool(ClassLoader cl);
+
+   /**
+    * Register a classloader
+    * 
+    * @param ucl the classloader
+    * @return the classpool
+    */
+   ClassPool registerClassLoader(ClassLoader ucl);
+
+   /**
+    * Get the registered classloaders
+    * 
+    * @return the registered classloaders
+    */
+   Map getRegisteredCLs();
+
+   /**
+    * This method will check to see if a register classloader has been undeployed (as in JBoss)
+    */
+   void clearUnregisteredClassLoaders();
+
+   /**
+    * Unregisters a classpool and unregisters its classloader.
+    * @ClassLoader the classloader the pool is stored under
+    */
+   void unregisterClassLoader(ClassLoader cl);
+}
\ No newline at end of file
diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java
new file mode 100644 (file)
index 0000000..6fca47e
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/ 
+package javassist.scopedpool;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javassist.ClassPool;
+import javassist.LoaderClassPath;
+
+/**
+ * 
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @version $Revision: 1.1 $
+ */
+public class ScopedClassPoolRepositoryImpl implements ScopedClassPoolRepository
+{
+   /** The instance */
+   private static final ScopedClassPoolRepositoryImpl instance = new ScopedClassPoolRepositoryImpl();
+
+   /** Whether to prune */
+   private boolean prune = true;
+
+   /** Whether to prune when added to the classpool's cache */
+   boolean pruneWhenCached;
+
+   /** The registered classloaders */
+   protected Map registeredCLs = Collections.synchronizedMap(new WeakHashMap());
+   
+   /** The default class pool */
+   protected ClassPool classpool;
+   
+   /** The factory for creating class pools */
+   protected ScopedClassPoolFactory factory = new ScopedClassPoolFactoryImpl();
+
+   /**
+    * Get the instance
+    * 
+    * @return the instance
+    */
+   public static ScopedClassPoolRepository getInstance()
+   {
+      return instance;
+   }
+
+   /**
+    * Singleton
+    */
+   private ScopedClassPoolRepositoryImpl()
+   {
+      classpool = ClassPool.getDefault();
+      // FIXME This doesn't look correct 
+      ClassLoader cl = Thread.currentThread().getContextClassLoader();
+      classpool.insertClassPath(new LoaderClassPath(cl));
+   }
+   
+   /**
+    * Get the prune.
+    * 
+    * @return the prune.
+    */
+   public boolean isPrune()
+   {
+      return prune;
+   }
+
+   /**
+    * Set the prune.
+    * 
+    * @param prune the prune.
+    */
+   public void setPrune(boolean prune)
+   {
+      this.prune = prune;
+   }
+
+   /**
+    * Create a scoped classpool
+    * 
+    * @param cl the classloader
+    * @param src the original classpool
+    * @return the classpool
+    */
+   public ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src)
+   {
+      return factory.create(cl, src, this);
+   }
+
+   public ClassPool findClassPool(ClassLoader cl)
+   {
+      if (cl == null)
+         return registerClassLoader(ClassLoader.getSystemClassLoader());
+      return registerClassLoader(cl);
+   }
+
+   /**
+    * Register a classloader
+    * 
+    * @param ucl the classloader
+    * @return the classpool
+    */
+   public ClassPool registerClassLoader(ClassLoader ucl)
+   {
+      synchronized (registeredCLs)
+      {
+         // FIXME: Probably want to take this method out later
+         // so that AOP framework can be independent of JMX
+         // This is in here so that we can remove a UCL from the ClassPool as a
+         // ClassPool.classpath
+         if (registeredCLs.containsKey(ucl))
+         {
+            return (ClassPool) registeredCLs.get(ucl);
+         }
+         ScopedClassPool pool = createScopedClassPool(ucl, classpool);
+         registeredCLs.put(ucl, pool);
+         return pool;
+      }
+   }
+
+   /**
+    * Get the registered classloaders
+    * 
+    * @return the registered classloaders
+    */
+   public Map getRegisteredCLs()
+   {
+      clearUnregisteredClassLoaders();
+      return registeredCLs;
+   }
+
+   /**
+    * This method will check to see if a register classloader has been undeployed (as in JBoss)
+    */
+   public void clearUnregisteredClassLoaders()
+   {
+      ArrayList toUnregister = null;
+      synchronized (registeredCLs)
+      {
+         Iterator it = registeredCLs.values().iterator();
+         while (it.hasNext())
+         {
+            ScopedClassPool pool = (ScopedClassPool) it.next();
+            if (pool.isUnloadedClassLoader())
+            {
+               it.remove();
+               ClassLoader cl = pool.getClassLoader();
+               if (cl != null)
+               {
+                  if (toUnregister == null)
+                  {
+                     toUnregister = new ArrayList();
+                  }
+                  toUnregister.add(cl);
+               }
+            }
+         }
+         if (toUnregister != null)
+         {
+            for (int i = 0; i < toUnregister.size(); i++)
+            {
+               unregisterClassLoader((ClassLoader) toUnregister.get(i));
+            }
+         }
+      }
+   }
+
+   public void unregisterClassLoader(ClassLoader cl)
+   {
+      synchronized (registeredCLs)
+      {
+         ScopedClassPool pool = (ScopedClassPool) registeredCLs.remove(cl);
+         if (pool != null) pool.close();
+      }
+   }
+
+   public void insertDelegate(ScopedClassPoolRepository delegate)
+   {
+      //Noop - this is the end
+   }
+
+   public void setClassPoolFactory(ScopedClassPoolFactory factory)
+   {
+      this.factory = factory;
+   }
+   
+   public ScopedClassPoolFactory getClassPoolFactory()
+   {
+      return factory;
+   }
+}
diff --git a/src/main/javassist/scopedpool/SoftValueHashMap.java b/src/main/javassist/scopedpool/SoftValueHashMap.java
new file mode 100644 (file)
index 0000000..b236160
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package javassist.scopedpool;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This Map will remove entries when the value in the map has been
+ * cleaned from garbage collection
+ *
+ * @version <tt>$Revision: 1.1 $</tt>
+ * @author  <a href="mailto:bill@jboss.org">Bill Burke</a>
+ */
+public class SoftValueHashMap
+   extends AbstractMap
+   implements Map 
+{
+   private static class SoftValueRef extends SoftReference
+   {
+      public Object key;
+
+      private SoftValueRef(Object key, Object val, ReferenceQueue q)
+      {
+         super(val, q);
+         this.key = key;
+      }
+      
+      private static SoftValueRef create(Object key, Object val, ReferenceQueue q)
+      {
+         if (val == null) return null;
+         else return new SoftValueRef(key, val, q);
+      }
+      
+   }
+   public Set entrySet() 
+   { 
+      processQueue();
+      return hash.entrySet();
+   }
+
+   /* Hash table mapping WeakKeys to values */
+   private Map hash;
+
+   /* Reference queue for cleared WeakKeys */
+   private ReferenceQueue queue = new ReferenceQueue();
+   
+   /* Remove all invalidated entries from the map, that is, remove all entries
+      whose values have been discarded.  
+    */
+   private void processQueue()
+   {
+      SoftValueRef ref;
+      while ((ref = (SoftValueRef)queue.poll()) != null) {
+         if (ref == (SoftValueRef) hash.get(ref.key)) {
+            // only remove if it is the *exact* same WeakValueRef
+            //
+            hash.remove(ref.key);
+         }
+      }
+   }
+
+
+   /* -- Constructors -- */
+
+   /**
+    * Constructs a new, empty <code>WeakHashMap</code> with the given
+    * initial capacity and the given load factor.
+    *
+    * @param  initialCapacity  The initial capacity of the
+    *                          <code>WeakHashMap</code>
+    *
+    * @param  loadFactor       The load factor of the <code>WeakHashMap</code>
+    *
+    * @throws IllegalArgumentException  If the initial capacity is less than
+    *                                   zero, or if the load factor is
+    *                                   nonpositive
+    */
+   public SoftValueHashMap(int initialCapacity, float loadFactor)
+   {
+      hash = new HashMap(initialCapacity, loadFactor);
+   }
+
+   /**
+    * Constructs a new, empty <code>WeakHashMap</code> with the given
+    * initial capacity and the default load factor, which is
+    * <code>0.75</code>.
+    *
+    * @param  initialCapacity  The initial capacity of the
+    *                          <code>WeakHashMap</code>
+    *
+    * @throws IllegalArgumentException  If the initial capacity is less than
+    *                                   zero
+    */
+   public SoftValueHashMap(int initialCapacity)
+   {
+      hash = new HashMap(initialCapacity);
+   }
+
+   /**
+    * Constructs a new, empty <code>WeakHashMap</code> with the default
+    * initial capacity and the default load factor, which is
+    * <code>0.75</code>.
+    */
+   public SoftValueHashMap()
+   {
+      hash = new HashMap();
+   }
+
+   /**
+    * Constructs a new <code>WeakHashMap</code> with the same mappings as the
+    * specified <tt>Map</tt>.  The <code>WeakHashMap</code> is created with an
+    * initial capacity of twice the number of mappings in the specified map
+    * or 11 (whichever is greater), and a default load factor, which is
+    * <tt>0.75</tt>.
+    *
+    * @param   t the map whose mappings are to be placed in this map.
+    * @since   1.3
+    */
+   public SoftValueHashMap(Map t)
+   {
+      this(Math.max(2*t.size(), 11), 0.75f);
+      putAll(t);
+   }
+
+   /* -- Simple queries -- */
+
+   /**
+    * Returns the number of key-value mappings in this map.
+    * <strong>Note:</strong> <em>In contrast with most implementations of the
+    * <code>Map</code> interface, the time required by this operation is
+    * linear in the size of the map.</em>
+    */
+   public int size()
+   {
+      processQueue();
+      return hash.size();
+   }
+
+   /**
+    * Returns <code>true</code> if this map contains no key-value mappings.
+    */
+   public boolean isEmpty()
+   {
+      processQueue();
+      return hash.isEmpty();
+   }
+
+   /**
+    * Returns <code>true</code> if this map contains a mapping for the
+    * specified key.
+    *
+    * @param   key   The key whose presence in this map is to be tested
+    */
+   public boolean containsKey(Object key)
+   {
+      processQueue();
+      return hash.containsKey(key);
+   }
+
+   /* -- Lookup and modification operations -- */
+
+   /**
+    * Returns the value to which this map maps the specified <code>key</code>.
+    * If this map does not contain a value for this key, then return
+    * <code>null</code>.
+    *
+    * @param  key  The key whose associated value, if any, is to be returned
+    */
+   public Object get(Object key)
+   {
+      processQueue();
+      SoftReference ref = (SoftReference)hash.get(key);
+      if (ref != null) return ref.get();
+      return null;
+   }
+
+   /**
+    * Updates this map so that the given <code>key</code> maps to the given
+    * <code>value</code>.  If the map previously contained a mapping for
+    * <code>key</code> then that mapping is replaced and the previous value is
+    * returned.
+    *
+    * @param  key    The key that is to be mapped to the given
+    *                <code>value</code> 
+    * @param  value  The value to which the given <code>key</code> is to be
+    *                mapped
+    *
+    * @return  The previous value to which this key was mapped, or
+    *          <code>null</code> if if there was no mapping for the key
+    */
+   public Object put(Object key, Object value) 
+   {
+      processQueue();
+      Object rtn = hash.put(key, SoftValueRef.create(key, value, queue));
+      if (rtn != null) rtn = ((SoftReference)rtn).get();
+      return rtn;
+   }
+
+   /**
+    * Removes the mapping for the given <code>key</code> from this map, if
+    * present.
+    *
+    * @param  key  The key whose mapping is to be removed
+    *
+    * @return  The value to which this key was mapped, or <code>null</code> if
+    *          there was no mapping for the key
+    */
+   public Object remove(Object key) 
+   {
+      processQueue();
+      return hash.remove(key);
+   }
+
+   /**
+    * Removes all mappings from this map.
+    */
+   public void clear()
+   {
+      processQueue();
+      hash.clear();
+   }
+}