You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by pe...@apache.org on 2010/08/18 11:00:01 UTC
svn commit: r986600 - in /incubator/river/jtsk/trunk/src:
net/jini/security/Security.java
org/apache/river/api/security/ExecutionContextManager.java
org/apache/river/imp/security/policy/se/ECM.java
org/apache/river/imp/util/ConcurrentSoftIdentityMap.java
Author: peter_firmstone
Date: Wed Aug 18 09:00:01 2010
New Revision: 986600
URL: http://svn.apache.org/viewvc?rev=986600&view=rev
Log:
ExecutionContextManager implementation for the RevokableDynamicPolicy, please review.
Added:
incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java (with props)
Modified:
incubator/river/jtsk/trunk/src/net/jini/security/Security.java
incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java
incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java
Modified: incubator/river/jtsk/trunk/src/net/jini/security/Security.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/net/jini/security/Security.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/net/jini/security/Security.java (original)
+++ incubator/river/jtsk/trunk/src/net/jini/security/Security.java Wed Aug 18 09:00:01 2010
@@ -26,6 +26,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.AccessControlContext;
+import java.security.AccessControlException;
import java.security.AccessController;
import java.security.DomainCombiner;
import java.security.Permission;
@@ -50,6 +51,8 @@ import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
import net.jini.security.policy.DynamicPolicy;
import net.jini.security.policy.SecurityContextSource;
+import org.apache.river.api.security.ExecutionContextManager;
+import org.apache.river.api.security.RevokeableDynamicPolicy;
/**
* Provides methods for executing actions with privileges enabled, for
@@ -166,7 +169,11 @@ public final class Security {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() { return new ClassContextAccess(); }
});
-
+ /**
+ * ExecutionContextManager
+ * null unless found, never returned.
+ */
+ private static volatile ExecutionContextManager ecm = null;
/**
* Non-instantiable.
*/
@@ -550,6 +557,67 @@ public final class Security {
}
/**
+ * <p>
+ * An optimised permission check, using the following strategies to
+ * reduce the cost of checking Permission:
+ * </p>
+ * <OL>
+ * <LI>Multiple permission checks may be performed together, the current
+ * execution AccessControlContext is only obtained once per invocation
+ * of this method, since identified as an expensive native code call.
+ * The returned AccessControlContext is used for all Permission checks.
+ * <LI>The result from an AccessControlContext.checkPermission(Permission)
+ * call is cached for that AccessControlContext, so checkPermission will
+ * only be called once, if it succeeds. If a RevokeableDynamicPolicy
+ * is installed, checkPermission may be called again, on a following
+ * invocation if a Permission revocation has cleared it's result from the cache.
+ * <LI>Permission's are only stored in the cache based on equality, implies
+ * is not called in the cache. This doesn't widen the scope of the cache
+ * since the tradeoff is that some implies calls are expensive.
+ * <LI>The cache used is backed by a SoftReference based ConcurrentHashMap
+ * </OL>
+ * <p>
+ * This is designed to be used in combination with Permission checks
+ * for Permission's that are Revokeable, but may also be used for optimising other
+ * non revokeable Permission checks. A non revokeable Permission is a
+ * Permission that guards a reference or prevents object creation, but
+ * does not prevent use of a privelege or object, if it reference escapes.
+ * see the RevokeableDynamicPolicy for details.
+ * </p><p>
+ * By lessening the cost of Permission calls it is intended that the performance
+ * impact of security checking be reduced sufficiently to provide a
+ * wider scope for revokeable Permissions.
+ * </p><p>
+ * If a RevokeableDynamicPolicy is not installed, this method will call
+ * AccessController.checkPermission, without any optimisations.
+ * </p>
+ * @param permissions
+ * @throws java.lang.NullPointerException
+ * @throws java.security.AccessControlException
+ * @see RevokeableDynamicPolicy
+ * @see ExecutionContextManager
+ * @see AccessController
+ * @see AccessControlContext
+ */
+ public static void checkPermission(Collection<Permission> permissions)
+ throws NullPointerException, AccessControlException
+ {
+ if (ecm == null){
+ Policy policy = getPolicy();
+ if (!(policy instanceof RevokeableDynamicPolicy)) {
+ Iterator<Permission> perms = permissions.iterator();
+ while (perms.hasNext()){
+ Permission perm = perms.next();
+ AccessController.checkPermission(perm);
+ }
+ return;
+ }
+ ecm = ((RevokeableDynamicPolicy) policy).getExecutionContextManager();
+ }
+ ecm.checkPermission(permissions);
+ }
+
+ /**
* Creates privileged context that contains the protection domain of the
* given caller class (if non-null) and uses the domain combiner of the
* specified context. This method assumes it is called from within a
Modified: incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java (original)
+++ incubator/river/jtsk/trunk/src/org/apache/river/api/security/ExecutionContextManager.java Wed Aug 18 09:00:01 2010
@@ -20,6 +20,7 @@ package org.apache.river.api.security;
import java.security.AccessControlException;
import java.security.AccessControlContext;
import java.security.Permission;
+import java.util.Collection;
/**
* <p>
@@ -64,7 +65,7 @@ import java.security.Permission;
*/
public interface ExecutionContextManager {
- /**
+ /*
* <p>
* Marks the beginning of Management of the Execution context, of the
* AccessControlContext and submits a reaper to intercept and clean up
@@ -91,7 +92,8 @@ public interface ExecutionContextManager
* the current thread is not interrupted, rather the reaper is expected
* to know what resources need to be closed.
*/
- void begin(Reaper r);
+ // To Be removed
+ //void begin(Reaper r);
/**
* <p>
@@ -116,9 +118,8 @@ public interface ExecutionContextManager
* </p><p>
* ExecutionContextManager provides a more thorough form of protection.
* </p><p>
- * ExecutionContextManager should be used sparingly and only for repeated
- * calls, if permission checking only happens occasionaly, use the
- * AccessController or SecurityManager.
+ * ExecutionContextManager should be used for repeated
+ * calls, it caches the results from the AccessControlContext.
* </p><p>
* Clients using the ExecutionContextManager, should be careful
* to release references to their Permission objects,
@@ -136,14 +137,14 @@ public interface ExecutionContextManager
* has been called.
* </p>
*
- * @param p Permission to be checked, if result not already in cache.
+ * @param perms Permissions to be checked, if result not already in cache.
* @throws java.security.AccessControlException
* @throws java.lang.NullPointerException
*/
- public void checkPermission(Permission p) throws AccessControlException,
+ public void checkPermission(Collection<Permission> perms) throws AccessControlException,
NullPointerException;
- /**
+ /*
* <p>
* This method is to advise the ExecutionContextManager that the
* current method or protected region has returned, it must
@@ -178,5 +179,6 @@ public interface ExecutionContextManager
* This should not be confused with AccessController.doPrivileged blocks
* </p>
*/
- void end();
+ // To be removed
+ //void end();
}
\ No newline at end of file
Modified: incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java?rev=986600&r1=986599&r2=986600&view=diff
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java (original)
+++ incubator/river/jtsk/trunk/src/org/apache/river/imp/security/policy/se/ECM.java Wed Aug 18 09:00:01 2010
@@ -23,6 +23,7 @@ import java.security.AccessControlExcept
import java.security.AccessController;
import java.security.Permission;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -47,16 +48,24 @@ import org.apache.river.imp.util.Concurr
* Only a Single instance of ECM is required per policy, it is threadsafe.
* Threads will wait until revoke is complete.
*
+ * Implementers Note: It hasn't been determined if the cache should be
+ * Map<AccessControlContext,Set<Permission>> or the opposite as implemented,
+ * in any case the emphasis needs to be placed on the permission check
+ * since these are called the most. There will be less exposure to hashCode
+ * and equals() implementation issues if the current cache structure is reversed.
+ * However the performance of AccessControlContext.hashCode needs to be
+ * determined first.
+ *
* @author Peter Firmstone
*/
class ECM implements ExecutionContextManager{
private final ConcurrentMap<Permission,Set<AccessControlContext>> checkedCache;
- private final ConcurrentMap<AccessControlContext, Set<Thread>> executionCache;
- private final ConcurrentMap<Thread, Set<AccessControlContext>> threadAssociation;
- private final ConcurrentMap<Thread, Reaper> association;
- private final ExecutorService executor;
- private final ReadWriteLock blockLock;
+ //private final ConcurrentMap<AccessControlContext, Set<Thread>> executionCache;
+ //private final ConcurrentMap<Thread, Set<AccessControlContext>> threadAssociation;
+ //private final ConcurrentMap<Thread, Reaper> association;
+ //private final ExecutorService executor;
+ private final ReadWriteLock revokeLock;
private final Lock rl; // This lock is held briefly by callers of begin and end.
private final Lock wl; // This lock is held by revocation.
@@ -69,21 +78,21 @@ class ECM implements ExecutionContextMan
* strong reference from the thread, which is removed when end()
* is executed.
*/
- executionCache = new ConcurrentWeakIdentityMap<AccessControlContext, Set<Thread>>();
+ //executionCache = new ConcurrentWeakIdentityMap<AccessControlContext, Set<Thread>>();
/* Thread association is utilised to track a thread as it enters and
* leaves the ExecutionContextManager try finally block.
*/
- threadAssociation = new ConcurrentHashMap<Thread, Set<AccessControlContext>>();
+ //threadAssociation = new ConcurrentHashMap<Thread, Set<AccessControlContext>>();
/* The association is only made while threads are within the clients
* try finally block.
*/
- association = new ConcurrentHashMap<Thread, Reaper>();
+ //association = new ConcurrentHashMap<Thread, Reaper>();
// TODO: Analyse needs, enable client configuration of thread pool.
- executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1);
+ //executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1);
/* This lock guards revocation */
- blockLock = new ReentrantReadWriteLock();
- rl = blockLock.readLock();
- wl = blockLock.writeLock();
+ revokeLock = new ReentrantReadWriteLock();
+ rl = revokeLock.readLock();
+ wl = revokeLock.writeLock();
}
void revoke(Set<Permission> perms) throws InterruptedException, ExecutionException{
@@ -122,137 +131,144 @@ class ECM implements ExecutionContextMan
permClasses.add(itp.next().getClass());
}
// Remove Permission's and AccessControlContexts from the checked cache.
- Map<Permission, Set<AccessControlContext>> removed =
- new HashMap<Permission, Set<AccessControlContext>>();
+// Map<Permission, Set<AccessControlContext>> removed =
+// new HashMap<Permission, Set<AccessControlContext>>();
Iterator<Permission> keysIt = checkedCache.keySet().iterator();
while (keysIt.hasNext()){
Permission p = keysIt.next();
if (permClasses.contains(p.getClass())){
Set<AccessControlContext> a = checkedCache.get(p);
keysIt.remove();
- removed.put(p, a);
- }
- }
- // Match the AccessControlContexts with the execution cache;
- Set<AccessControlContext> exCache = executionCache.keySet();
- // Get the AccessControlContext's in the execution cache that fail.
- Set<AccessControlContext> accFails = new HashSet<AccessControlContext>();
- Iterator<Permission> retests = removed.keySet().iterator();
- while (retests.hasNext()){
- Permission p = retests.next();
- Set<AccessControlContext> rechecks = removed.get(p);
- Iterator<AccessControlContext> recheck = rechecks.iterator();
- while (recheck.hasNext()){
- AccessControlContext a = recheck.next();
- if (accFails.contains(a)) continue;
- // This really narrows down the checks.
- if (exCache.contains(a)){
- try {
- a.checkPermission(p);
- } catch (AccessControlException e){
- accFails.add(a);
- }
- }
- }
- }
- // Identify the threads and prepare reapers.
- Set<Runnable> reapers = new HashSet<Runnable>();
- Iterator<AccessControlContext> failedAcc = accFails.iterator();
- while (failedAcc.hasNext()){
- AccessControlContext fail = failedAcc.next();
- Set<Thread> threads = executionCache.get(fail);
- Iterator<Thread> i = threads.iterator();
- while (i.hasNext()) {
- Thread t = i.next();
- Reaper r = association.get(t);
- if ( r == null ) continue;
- r.put(t);
- reapers.add(r);
+// removed.put(p, a);
}
}
- /* Process the reapers, this requires a thread pool, but we don't
- * want to return until all reapers have completed.
- */
- Iterator<Runnable> reaper = reapers.iterator();
- List<Future> results = new ArrayList<Future>(reapers.size());
- while ( reaper.hasNext()) {
- results.add(executor.submit(reaper.next()));
- }
- Iterator<Future> result = results.iterator();
- while (result.hasNext()){
- // Waits for result.
- result.next().get();
- }
+// // Match the AccessControlContexts with the execution cache;
+// Set<AccessControlContext> exCache = executionCache.keySet();
+// // Get the AccessControlContext's in the execution cache that fail.
+// Set<AccessControlContext> accFails = new HashSet<AccessControlContext>();
+// Iterator<Permission> retests = removed.keySet().iterator();
+// while (retests.hasNext()){
+// Permission p = retests.next();
+// Set<AccessControlContext> rechecks = removed.get(p);
+// Iterator<AccessControlContext> recheck = rechecks.iterator();
+// while (recheck.hasNext()){
+// AccessControlContext a = recheck.next();
+// if (accFails.contains(a)) continue;
+// // This really narrows down the checks.
+// if (exCache.contains(a)){
+// try {
+// a.checkPermission(p);
+// } catch (AccessControlException e){
+// accFails.add(a);
+// }
+// }
+// }
+// }
+// // Identify the threads and prepare reapers.
+// Set<Runnable> reapers = new HashSet<Runnable>();
+// Iterator<AccessControlContext> failedAcc = accFails.iterator();
+// while (failedAcc.hasNext()){
+// AccessControlContext fail = failedAcc.next();
+// Set<Thread> threads = executionCache.get(fail);
+// Iterator<Thread> i = threads.iterator();
+// while (i.hasNext()) {
+// Thread t = i.next();
+// Reaper r = association.get(t);
+// if ( r == null ) continue;
+// r.put(t);
+// reapers.add(r);
+// }
+// }
+// /* Process the reapers, this requires a thread pool, but we don't
+// * want to return until all reapers have completed.
+// */
+// Iterator<Runnable> reaper = reapers.iterator();
+// List<Future> results = new ArrayList<Future>(reapers.size());
+// while ( reaper.hasNext()) {
+// results.add(executor.submit(reaper.next()));
+// }
+// Iterator<Future> result = results.iterator();
+// while (result.hasNext()){
+// // Waits for result.
+// result.next().get();
+// }
/* We're done, go home & rest */
} finally {
wl.unlock();
}
}
- public void begin(Reaper r) {
- Thread currentThread = Thread.currentThread();
- if ( r == null ) return;
- association.put(currentThread, r);
- }
+// public void begin(Reaper r) {
+// Thread currentThread = Thread.currentThread();
+// if ( r == null ) return;
+// association.put(currentThread, r);
+// }
- public void checkPermission(Permission p) throws AccessControlException {
- if (p == null ) throw new NullPointerException("Permission null");
- Thread currentThread = Thread.currentThread();
+ public void checkPermission(Collection<Permission> perms) throws AccessControlException {
+ if (perms == null ) throw new NullPointerException("Permission Collection null");
+ if (perms.isEmpty()) return; // Should we do this or is it a bug?
+ //Thread currentThread = Thread.currentThread();
AccessControlContext executionContext = AccessController.getContext();
+ HashSet<Permission> permissions = new HashSet<Permission>(perms.size());
+ permissions.addAll(perms);
rl.lock();
try {
- // execution cache, fast for repeated calls.
- Set<Thread> exCacheThreadSet = executionCache.get(executionContext);
- if ( exCacheThreadSet == null ){
- exCacheThreadSet = Collections.synchronizedSet(new HashSet<Thread>());
- Set<Thread> existed = executionCache.putIfAbsent(executionContext, exCacheThreadSet);
- if (existed != null){
- exCacheThreadSet = existed;
- }
- }
- exCacheThreadSet.add(currentThread);// end execution cache.
- // thread association, fast for repeated calls.
- Set<AccessControlContext> thAssocSet = threadAssociation.get(currentThread);
- if ( thAssocSet == null ){
- thAssocSet = Collections.synchronizedSet(new HashSet<AccessControlContext>());
- Set<AccessControlContext> existed = threadAssociation.putIfAbsent(currentThread, thAssocSet);
- if (existed != null){
- thAssocSet = existed;
- }
- }
- thAssocSet.add(executionContext); // end thread association.
+// // execution cache, fast for repeated calls.
+// Set<Thread> exCacheThreadSet = executionCache.get(executionContext);
+// if ( exCacheThreadSet == null ){
+// exCacheThreadSet = Collections.synchronizedSet(new HashSet<Thread>());
+// Set<Thread> existed = executionCache.putIfAbsent(executionContext, exCacheThreadSet);
+// if (existed != null){
+// exCacheThreadSet = existed;
+// }
+// }
+// exCacheThreadSet.add(currentThread);// end execution cache.
+// // thread association, fast for repeated calls.
+// Set<AccessControlContext> thAssocSet = threadAssociation.get(currentThread);
+// if ( thAssocSet == null ){
+// thAssocSet = Collections.synchronizedSet(new HashSet<AccessControlContext>());
+// Set<AccessControlContext> existed = threadAssociation.putIfAbsent(currentThread, thAssocSet);
+// if (existed != null){
+// thAssocSet = existed;
+// }
+// }
+// thAssocSet.add(executionContext); // end thread association.
// checkedCache - the permission check, fast for repeated calls.
- Set<AccessControlContext> checked = checkedCache.get(p);
- if (checked == null ){
- checked = Collections.synchronizedSet(new HashSet<AccessControlContext>());
- Set<AccessControlContext> existed = checkedCache.putIfAbsent(p, checked);
- if (existed != null){
- checked = existed;
+ Iterator<Permission> permiter = permissions.iterator();
+ while (permiter.hasNext()){
+ Permission p = permiter.next();
+ Set<AccessControlContext> checked = checkedCache.get(p);
+ if (checked == null ){
+ checked = Collections.synchronizedSet(new HashSet<AccessControlContext>());
+ Set<AccessControlContext> existed = checkedCache.putIfAbsent(p, checked);
+ if (existed != null){
+ checked = existed;
+ }
}
+ if ( checked.contains(executionContext)) continue; // it's passed before.
+ executionContext.checkPermission(p); // Throws AccessControlException
+ // If we get here cache the AccessControlContext.
+ checked.add(executionContext); // end checkedCache.
}
- if ( checked.contains(executionContext)) return; // it's passed before.
- executionContext.checkPermission(p); // Throws AccessControlException
- // If we get here cache the AccessControlContext.
- checked.add(executionContext); // end checkedCache.
} finally {
rl.unlock();
}
}
- public void end() {
- // Removal from execution cache.
- Thread t = Thread.currentThread();
- rl.lock();
- try {
- association.remove(t);
- Set<AccessControlContext> accSet = threadAssociation.remove(t);
- Iterator<AccessControlContext> it = accSet.iterator();
- while (it.hasNext()){
- AccessControlContext acc = it.next();
- executionCache.get(acc).remove(t);
- }
- }finally {
- rl.unlock();
- }
- }
+// public void end() {
+// // Removal from execution cache.
+// Thread t = Thread.currentThread();
+// rl.lock();
+// try {
+// association.remove(t);
+// Set<AccessControlContext> accSet = threadAssociation.remove(t);
+// Iterator<AccessControlContext> it = accSet.iterator();
+// while (it.hasNext()){
+// AccessControlContext acc = it.next();
+// executionCache.get(acc).remove(t);
+// }
+// }finally {
+// rl.unlock();
+// }
+// }
}
Added: incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java?rev=986600&view=auto
==============================================================================
--- incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java (added)
+++ incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java Wed Aug 18 09:00:01 2010
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.river.imp.util;
+
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Identity-based sofly referenced hash map, safe for concurrent threads.
+ *
+ * Based on an underlying ConcurrentHashMap, it doesn't accept null keys.
+ *
+ *
+ * @param K - key
+ * @param V - value
+ * @author Peter Firmstone.
+ *
+ * @since 2.3
+ */
+public class ConcurrentSoftIdentityMap<K, V> implements ConcurrentMap<K, V> {
+ // ConcurrentHashMap must be protected from null values;
+ private final ConcurrentHashMap<Key, V> map = new ConcurrentHashMap<Key, V>();
+ private final ReferenceQueue queue = new ReferenceQueue();
+
+ /**
+ * Associates value with given key, returning value previously associated
+ * with key, or null if none.
+ * @param key - Key
+ * @param value - Value
+ * @return previous value or null
+ */
+ public V put(K key, V value) {
+ processQueue();
+ if (key == null){return null;}
+ return map.put(Key.create(key, queue), value);
+ }
+
+ /**
+ * Returns value associated with given key, or null if none.
+ */
+ public V get(Object key) {
+ processQueue();
+ if (key == null) { return null;}
+ return map.get(Key.create(key, null));
+ }
+
+ /**
+ * Removes association for given key, returning value previously associated
+ * with key, or null if none.
+ */
+ public V remove(Object key) {
+ processQueue();
+ if (key == null) {return null;}
+ return map.remove(Key.create(key, null));
+ }
+
+ /**
+ * Returns collection containing all values currently held in this map.
+ */
+ public Collection<V> values() {
+ processQueue();
+ return map.values();
+ }
+
+ /**
+ * Removes all associations from this map.
+ */
+ public void clear() {
+ processQueue();
+ map.clear();
+ }
+
+ private void processQueue() {
+ Key k;
+ while ((k = (Key) queue.poll()) != null) {
+ map.remove(k);
+ }
+ }
+
+ private static class Key<T> extends SoftReference<T> {
+ private final int hash;
+
+ @SuppressWarnings("unchecked")
+ static Key create(Object k, ReferenceQueue q) {
+ //if (k == null) {return null;} // Perhaps this is incorrect
+ if (q == null) {return new Key(k);}
+ return new Key(k, q);
+ }
+
+ private Key(T k) {
+ super(k);
+ hash = System.identityHashCode(k);
+ }
+
+ private Key(T k, ReferenceQueue<? super T> q) {
+ super(k, q);
+ hash = System.identityHashCode(k);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (!(o instanceof Key)) {
+ return false;
+ }
+ Object k1 = get(), k2 = ((Key) o).get();
+ return (k1 != null && k2 != null && k1 == k2);
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+ }
+
+ public int size() {
+ processQueue();
+ return map.size();
+ }
+
+ public boolean isEmpty() {
+ processQueue();
+ return map.isEmpty();
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean containsKey(Object key) {
+ processQueue();
+ if (key == null) {return false;}
+ return map.containsKey(new Key(key));
+ }
+
+ public boolean containsValue(Object value) {
+ processQueue();
+ if (value == null) {return false;}
+ return map.containsValue(value);
+ }
+
+ /**
+ * Unsupported method
+ * @param m
+ */
+ public void putAll(Map<? extends K, ? extends V> m) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<K> keySet() {
+ processQueue();
+ Enumeration<Key> keys = map.keys(); //Defensive copy by ConcurrentHashMap
+ Set<K> keySet = new HashSet<K>();
+ while (keys.hasMoreElements()){
+ keySet.add( (K) keys.nextElement().get());
+ }
+ return keySet;
+ }
+
+ /**
+ * Unsupported method
+ * @return
+ */
+ public Set<Map.Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException("Not supported yet, ever?");
+ }
+
+ @SuppressWarnings("unchecked")
+ public V putIfAbsent(K key, V value) {
+ processQueue(); //may be a slight delay before atomic putIfAbsent
+ return map.putIfAbsent(new Key(key), value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean remove(Object key, Object value) {
+ return map.remove(new Key(key), value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean replace(K key, V oldValue, V newValue) {
+ processQueue();
+ return map.replace(new Key(key), oldValue, newValue);
+ }
+
+ @SuppressWarnings("unchecked")
+ public V replace(K key, V value) {
+ processQueue();
+ return map.replace(new Key(key), value);
+ }
+}
Propchange: incubator/river/jtsk/trunk/src/org/apache/river/imp/util/ConcurrentSoftIdentityMap.java
------------------------------------------------------------------------------
svn:eol-style = native