/* * 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 com.sun.jini.collection; import java.util.*; import java.lang.ref.*; import java.io.PrintStream; /** * This class is designed to allow weakly held keys to weakly held * objects. For example, it can be used for smart proxy objects that * maintain equality for references to the same remote server. If a * single VM twice invokes a remote method that returns a proxy for the * same JavaSpaces server, the references returned by that method * should be the same. This allows <code>==</code> tests to work for * proxies to remote servers the same as they would for direct references * to remote servers, which also maintain this property. * <p> * Here is an example that uses this class to ensure that exactly one * copy of a <code>java.io.Resolvable</code> object exists in each * VM: * <pre> * private WeakTable knownProxies; * * public Object readResolve() { * // deferred creation means this table is not allocated on the server * if (knownProxies == null) * knownProxies = new WeakTable(); * return knownProxies.getOrAdd(remoteServer, this); * } * </pre> * * @author Sun Microsystems, Inc. * */ public class WeakTable { /** The map of known objects. */ private HashMap table = new HashMap(); /** The queue of cleared SpaceProxy objects. */ private ReferenceQueue refQueue = new ReferenceQueue(); /** Print debug messages to this stream if not <code>null</code>. */ private static PrintStream DEBUG = null; /** Object to call back when keys are collected */ private KeyGCHandler handler = null; /** * Create a new WeakTable object to maintain the maps. */ public WeakTable() { if (DEBUG != null) DEBUG.println("Creating WeakTable"); table = new HashMap(); refQueue = new ReferenceQueue(); } /** * Create a new WeakTable object to maintain the maps that calls * back the designated object when keys are collected. */ public WeakTable(KeyGCHandler handler) { this(); this.handler = handler; } /** * Return the object that this key maps to. If it currently maps to * no object, set it to map to the given object. Return either the * existing entry or the new one, whichever is used. */ public synchronized Object getOrAdd(Object key, Object proxy) { Object existing = get(key); if (existing != null) { if (DEBUG != null) DEBUG.println("WeakTable.getOrAdd: found " + existing); return existing; } else { if (DEBUG != null) DEBUG.println("WeakTable.getOrAdd: adding " + proxy); table.put(new WeakKeyReference(key, refQueue), new WeakReference(proxy, refQueue)); return proxy; } } /** * Return the value associated with given key, or <code>null</code> * if no value can be found. */ public synchronized Object get(Object key) { removeBlanks(); WeakKeyReference keyRef = new WeakKeyReference(key); WeakReference ref = (WeakReference) table.get(keyRef); Object existing = (ref == null ? null : ref.get()); if (DEBUG != null) { DEBUG.println("WeakTable.get:ref = " + ref + ", existing = " + existing); } return existing; } /** * Remove the object that the given key maps to. If found return * the object, otherwise return null. */ public synchronized Object remove(Object key) { removeBlanks(); WeakKeyReference keyRef = new WeakKeyReference(key); WeakReference ref = (WeakReference) table.remove(keyRef); if (ref == null) return null; return ref.get(); } /** * Remove any blank entries from the table. This can be invoked * by a reaping thread if you like. */ /* * We only clear table entries when the key shows up in the queue, * since that is much more efficient. Since the key is usually the * remote reference, and since the remote reference is usually * referenced only from the proxy, the key reference should be * cleared around the same time as the proxy reference. If not, * then all the hangs around unnecessarily is this table entry, * which is small. This tradeoff seems worth the significant * efficiency of using the HashMap in the intended way -- efficient * key access. */ public synchronized void removeBlanks() { if (DEBUG != null) DEBUG.println("WeakTable.removeBlanks: starting"); Reference ref; while ((ref = refQueue.poll()) != null) { if (ref instanceof WeakKeyReference) { final WeakReference valref = (WeakReference)table.remove(ref); if (valref != null && handler != null && valref.get() != null) handler.keyGC(valref.get()); if (DEBUG != null) { boolean removed = (valref != null); DEBUG.print("WeakTable.removeBlanks: key=" + ref); DEBUG.println(", " + (removed ? "" : "!") + "removed, " + table.size() + " remain"); } } else { if (DEBUG != null) DEBUG.println("WeakTable.removeBlanks: value=" + ref); } } if (DEBUG != null) DEBUG.println("WeakTable.removeBlanks: finished"); } /** * Handler for clients that need to know when a key is removed * from the table because it has been collected. */ public static interface KeyGCHandler { /** Called by WeakTable when it notices that a key has been * collected and the value still exists. * @param value The value associated with the collected key */ public void keyGC(Object value); } }