/* * JCarder -- cards Java programs to keep threads disentangled * * Copyright (C) 2006-2007 Enea AB * Copyright (C) 2007 Ulrik Svensson * Copyright (C) 2007 Joel Rosdahl * * This program is made available under the GNU GPL version 2, with a special * exception for linking with JUnit. See the accompanying file LICENSE.txt for * details. * * This program 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. */ package com.enea.jcarder.agent; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.HashMap; import net.jcip.annotations.NotThreadSafe; import com.enea.jcarder.common.LockingContext; import com.enea.jcarder.common.contexts.ContextWriterIfc; import com.enea.jcarder.util.logging.Logger; /** * This class is responsible for mapping LockingContext instances to locking * context IDs. It maintains a cache to be able to return the same ID again if * an ID is requested for the same or equal LockingContext more than once. * * This class is similar to the java.util.WeakHashMap but uses soft references * instead of weak references in order to try to keep the entries in the cache * as long as there is enough memory available. * * TODO An alternative implementation to consider could be to only store the * hashCode in a map and perform a comparison with the Context file file * (possibly memory mapped). I don't know how that would affect the performance. * Another option to consider would be to use a plain HashMap without * SoftReferences and accept the potential memory problem as a trade-of for * better performance (?) and to avoid getting different IDs for duplicated * LockingContexts. * * TODO Add basic tests for this class. */ @NotThreadSafe final class LockingContextIdCache { private final HashMap<EqualsComparableKey, Integer> mCache; private final ReferenceQueue<Object> mReferenceQueue; private final ContextWriterIfc mContextWriter; private final Logger mLogger; /** * Create a LockingContextIdCache backed by a ContextWriterIfc. */ public LockingContextIdCache(Logger logger, ContextWriterIfc writer) { mLogger = logger; mCache = new HashMap<EqualsComparableKey, Integer>(); mReferenceQueue = new ReferenceQueue<Object>(); mContextWriter = writer; } /** * Acquire a unique ID for the provided LockingContext. The ID will be * cached. If a provided LockingContext is equal to a previously provided * LockingContext that is still in the cache, the same ID will be returned. * * The equality is checked with the LockingContext.equals(Object other) * method. */ public int acquireContextId(LockingContext context) throws IOException { assert context != null; removeGarbageCollectedKeys(); Integer id = mCache.get(new StrongKey(context)); if (id == null) { mLogger.finest("Creating new context ID"); id = mContextWriter.writeContext(context); mCache.put((new SoftKey(context, mReferenceQueue)), id); } return id; } private void removeGarbageCollectedKeys() { Reference e; while ((e = mReferenceQueue.poll()) != null) { mLogger.finest("Removing garbage-collected cached context"); mCache.remove(e); } } private static interface EqualsComparableKey { Object get(); boolean equals(Object obj); int hashCode(); } private static class StrongKey implements EqualsComparableKey { private final Object mReferent; StrongKey(Object referent) { assert referent != null; mReferent = referent; } public Object get() { return mReferent; } public boolean equals(Object obj) { if (this == obj) { return true; } try { EqualsComparableKey reference = (EqualsComparableKey) obj; return mReferent.equals(reference.get()); } catch (ClassCastException e) { return false; } } public int hashCode() { return mReferent.hashCode(); } } private static class SoftKey extends SoftReference<LockingContext> implements EqualsComparableKey { private final int mHash; SoftKey(LockingContext referent, ReferenceQueue<Object> queue) { super(referent, queue); assert referent != null; mHash = referent.hashCode(); } public boolean equals(Object obj) { if (this == obj) { return true; } try { Object otherReferent = ((EqualsComparableKey) obj).get(); Object thisReferent = get(); return (thisReferent != null && otherReferent != null && thisReferent.equals(otherReferent)); } catch (ClassCastException e) { return false; } } public int hashCode() { return mHash; } } }