/* * Copyright 2009 Google Inc. * * Licensed 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 com.google.gwt.core.client.impl; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * A class associating a (String, Object) map with arbitrary source objects * (except for Strings). This implementation is used in Development Mode. */ public class WeakMapping { /* * This implementation is used in Development Mode only. It uses a HashMap to * associate the (key, value) maps with source object instances. The object * instances are wrapped in IdentityWeakReference objects in order to both * allow the underlying objects to be garbage-collected and to apply * IdentityHashMap semantics so that distinct objects that happen to compare * as equals() still get to have distinct maps associated with them. */ /** * A WeakReference implementing equals() and hashCode(). The hash code of the * reference is permanently set to the identity hash code of the referent at * construction time. */ static class IdentityWeakReference extends WeakReference<Object> { /** * The identity hash code of the referent, cached during construction. */ private final int hashCode; public IdentityWeakReference(Object referent, ReferenceQueue<Object> queue) { super(referent, queue); hashCode = System.identityHashCode(referent); } @Override public boolean equals(Object other) { /* * Identical objects are always equal. */ if (this == other) { return true; } /* * We can only be equal to another IdentityWeakReference. */ if (!(other instanceof IdentityWeakReference)) { return false; } /* * Check equality of the underlying referents. If either referent is no * longer present, equals() will return false (note that the case of * identical IdentityWeakReference objects has already been defined to * return true above). */ Object referent = get(); if (referent == null) { return false; } return referent == ((IdentityWeakReference) other).get(); } @Override public int hashCode() { return hashCode; } } private static class ManagedWeakReference<T> extends WeakReference<T> { public ManagedWeakReference(T object) { super(object); } } /** * Provides synchronization around {@link #queue} so at most one thread is * cleaning at any given time. */ private static final Lock cleanupLock = new ReentrantLock(); /** * A Map from Objects to <String,Object> maps. Hashing is based on object * identity. Weak references are used to allow otherwise unreferenced Objects * to be garbage collected. */ private static final ConcurrentHashMap<IdentityWeakReference, Map<String, Object>> map = new ConcurrentHashMap<IdentityWeakReference, Map<String, Object>>(); /** * A ReferenceQueue used to clean up the map as its keys are * garbage-collected. */ private static ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); /** * Returns the Object associated with the given key in the (key, value) * mapping associated with the given Object instance. * * @param instance the source Object. * @param key a String key. * @return an Object associated with that key on the given instance, or null. */ public static Object get(Object instance, String key) { cleanup(); Object ref = new IdentityWeakReference(instance, queue); Map<String, Object> m = map.get(ref); if (m == null) { return null; } Object toReturn = m.get(key); if (toReturn instanceof ManagedWeakReference) { toReturn = ((ManagedWeakReference<?>) toReturn).get(); } return toReturn; } /** * Associates a value with a given key in the (key, value) mapping associated * with the given Object instance. Note that the key space is module-wide, so * some care should be taken to choose sufficiently unique identifiers. * * <p> * Due to restrictions of the Production Mode implementation, the instance * argument must not be a String. * * @param instance the source Object, which must not be a String. * @param key a String key. * @param value the Object to associate with the key on the given source * Object. * @throws IllegalArgumentException if instance is a String. */ public static void set(Object instance, String key, Object value) { cleanup(); if (instance instanceof String) { throw new IllegalArgumentException("Cannot use Strings with WeakMapping"); } IdentityWeakReference ref = new IdentityWeakReference(instance, queue); Map<String, Object> m = map.get(ref); if (m == null) { m = new ConcurrentHashMap<String, Object>(); map.putIfAbsent(ref, m); m = map.get(ref); } if (value == null) { m.remove(key); } else { m.put(key, value); } } /** * Like {@link #set(Object, String, Object)}, but doesn't guarantee that * {@code value} can be retrieved. */ public static void setWeak(Object instance, String key, Object value) { set(instance, key, new ManagedWeakReference<Object>(value)); } /** * Remove garbage-collected keys from the map. The (key, value) maps * associated with those keys will then become unreferenced themselves and * will be eligible for future garbage collection. */ private static void cleanup() { if (cleanupLock.tryLock()) { Reference<? extends Object> ref; while ((ref = queue.poll()) != null) { /** * Note that we can still remove ref from the map even though its * referent has been nulled out since we only need == equality to do so. */ map.remove(ref); } cleanupLock.unlock(); } } }