/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/) SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite Institut de Recherche pour le D�veloppement / US-Espace mailto:seasnet@teledetection.fr This library 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 library 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: klaus.greve@uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.model.csct.resources; // Collections import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.AbstractSet; import java.util.Arrays; import java.util.Iterator; /** * A set of object hold by weak references. * This class is used to implements caches. * * @version 1.0 * @author Martin Desruisseaux */ public class WeakHashSet extends AbstractSet { /** * A weak reference to an element. * * @version 1.0 * @author Martin Desruisseaux */ private final class WeakElement extends WeakReference { /** * The next entry, or <code>null</code> if there is none. */ WeakElement next; /** * Index for this element in {@link #table}. This index * must be updated at every {@link #rehash} call. */ int index; /** * Construct a new weak reference. */ WeakElement( final Object obj, final WeakElement next, final int index ) { super( obj, referenceQueue ); this.next = next; this.index = index; } /** * Clear the reference. */ public void clear() { super.clear(); remove( this ); } } /** * Minimal capacity for {@link #table}. */ private static final int MIN_CAPACITY = 7; /** * Load factor. Control the moment * where {@link #table} must be rebuild. */ private static final float LOAD_FACTOR = 0.75f; /** * List of reference collected by the garbage collector. * Those elements must be removed from {@link #table}. */ private static final ReferenceQueue referenceQueue = new ReferenceQueue(); /** * Background thread removing references * collected by the garbage collector. */ private static final Thread thread = new Thread( "WeakHashSet" ) { public void run() { while ( true ) try { referenceQueue.remove().clear(); } catch ( InterruptedException exception ) { // Somebody doesn't want to lets // us sleep... Go back to work. } catch ( Exception exception ) { exception.printStackTrace(); } } }; static { thread.setDaemon( true ); thread.start(); } /** * Table of weak references. */ private WeakElement[] table; /** * Number of non-nul elements in {@link #table}. */ private int count; /** * The next size value at which to resize. This value should * be <code>{@link #table}.length*{@link #loadFactor}</code>. */ private int threshold; /** * The timestamp when {@link #table} was last rehashed. This information * is used to avoir too early table reduction. When the garbage collector * collected a lot of elements, we will wait at least 20 seconds before * rehashing {@link #table} in order to avoir to many cycles "reduce", * "expand", "reduce", "expand", etc. */ private long lastRehashTime; /** * Number of millisecond to wait before to rehash * the table for reducing its size. */ private static final long HOLD_TIME = 20 * 1000L; /** * Construct a <code>WeakHashSet</code>. */ public WeakHashSet() { table = new WeakElement[MIN_CAPACITY]; threshold = Math.round( table.length * LOAD_FACTOR ); lastRehashTime = System.currentTimeMillis(); } /** * Invoked by {@link WeakElement} when an element has been collected * by the garbage collector. This method will remove the weak reference * from {@link #table}. */ private synchronized void remove( final WeakElement toRemove ) { final int i = toRemove.index; // Index 'i' may not be valid if the reference 'toRemove' // has been already removed in a previous rehash. if ( i < table.length ) { WeakElement prev = null; WeakElement e = table[i]; while ( e != null ) { if ( e == toRemove ) { if ( prev != null ) prev.next = e.next; else table[i] = e.next; count--; // If the number of elements has dimunished // significatively, rehash the table. if ( count <= threshold / 4 ) rehash( false ); // We must not continue the loop, since // variable 'e' is no longer valid. return; } prev = e; e = e.next; } } /* * If we reach this point, its mean that reference 'toRemove' has not * been found. This situation may occurs if 'toRemove' has already been * removed in a previous run of {@link #rehash}. */ } /** * Rehash {@link #table}. * * @param augmentation <code>true</code> if this method is invoked * for augmenting {@link #table}, or <code>false</code> if * it is invoked for making the table smaller. */ private void rehash( final boolean augmentation ) { final long currentTime = System.currentTimeMillis(); final int capacity = Math.max( Math.round( count / ( LOAD_FACTOR / 2 ) ), count + MIN_CAPACITY ); if ( augmentation ? ( capacity <= table.length ) : ( capacity >= table.length || currentTime - lastRehashTime < HOLD_TIME ) ) { return; } lastRehashTime = currentTime; final WeakElement[] oldTable = table; table = new WeakElement[capacity]; threshold = Math.round( capacity * LOAD_FACTOR ); for ( int i = 0; i < oldTable.length; i++ ) { for ( WeakElement old = oldTable[i]; old != null; ) { final WeakElement e = old; old = old.next; // On retient 'next' tout de suite car sa valeur va changer... final Object obj_e = e.get(); if ( obj_e != null ) { final int index = ( hashCode( obj_e ) & 0x7FFFFFFF ) % table.length; e.index = index; e.next = table[index]; table[index] = e; } else count--; } } } /** * Returns <code>true</code> if this set contains the specified element. * * @param obj Object to be checked for containment in this set. * @return <code>true</code> if this set contains the specified element. */ public boolean contains( final Object obj ) { return obj == null || get( obj ) != null; } /** * Returns an object equals to the specified object, if present. If * this set doesn't contains any object equals to <code>obj</code>, * then this method returns <code>null</code>. */ public synchronized final Object get( final Object obj ) { if ( obj != null ) { final int hash = hashCode( obj ) & 0x7FFFFFFF; int index = hash % table.length; for ( WeakElement e = table[index]; e != null; e = e.next ) { final Object e_obj = e.get(); if ( e_obj != null ) { if ( equals( obj, e_obj ) ) return e_obj; } } } return obj; } /** * Adds the specified element to this set if it is not already present. * If this set already contains the specified element, the call leaves * this set unchanged and returns <code>false</code>. * * @param obj Element to be added to this set. * @return <code>true</code> if this set did not already * contain the specified element. */ public synchronized boolean add( final Object obj ) { return intern0( obj ) == null; } /** * Returns an object equals to <code>obj</code> if such an object already * exist in this <code>WeakHashSet</code>. Otherwise, add <code>obj</code> * to this <code>WeakHashSet</code>. This method is equivalents to the * following code: * * <blockquote><pre> *   if (object!=null) *   { *   final Object current=get(object); *   if (current!=null) return current; *   else add(object); *   } *   return object; * </pre></blockquote> */ private Object intern0( final Object obj ) { if ( obj != null ) { /* * Check if <code>obj</code> is already contained in this * <code>WeakHashSet</code>. If yes, returns the element. */ final int hash = hashCode( obj ) & 0x7FFFFFFF; int index = hash % table.length; for ( WeakElement e = table[index]; e != null; e = e.next ) { final Object e_obj = e.get(); if ( e_obj != null ) { if ( equals( obj, e_obj ) ) return e_obj; } // Do not remove the null element; lets "remove" do its job // (it was a bug to remove element here as an "optimization") } /* * Check if the table need to be rehashed, * and add <code>obj</code> to the table. */ if ( count >= threshold ) { rehash( true ); index = hash % table.length; } table[index] = new WeakElement( obj, table[index], index ); count++; } return obj; } /** * Returns an object equals to <code>obj</code> if such an object already * exist in this <code>WeakHashSet</code>. Otherwise, add <code>obj</code> * to this <code>WeakHashSet</code>. This method is equivalents to the * following code: * * <blockquote><pre> *   if (object!=null) *   { *   final Object current=get(object); *   if (current!=null) return current; *   else add(object); *   } *   return object; * </pre></blockquote> */ public synchronized final Object intern( final Object object ) { return intern0( object ); } /** * Iteratively call {@link #intern(Object)} for an array of objects. * This method is equivalents to the following code: * * <blockquote><pre> *   for (int i=0; i<objects.length; i++) *   objects[i] = intern(objects[i]); * </pre></blockquote> */ public synchronized final void intern( final Object[] objects ) { for ( int i = 0; i < objects.length; i++ ) objects[i] = intern0( objects[i] ); } /** * Returns the count of element in this set. */ public synchronized final int size() { return count; } /** * Removes all of the elements from this set. */ public synchronized final void clear() { Arrays.fill( table, null ); count = 0; } /** * Returns a view of this set as an array. Elements will be in an arbitrary * order. Note that this array contains strong reference. Consequently, no * object reclamation will occurs as long as a reference to this array is hold. */ public synchronized final Object[] toArray() { final Object[] elements = new Object[count]; int index = 0; for ( int i = 0; i < table.length; i++ ) { for ( WeakElement el = table[i]; el != null; el = el.next ) { if ( ( elements[index] = el.get() ) != null ) index++; } } return XArray.resize( elements, index ); } /** * Returns an iterator over the elements contained in this collection. * No element from this set will be garbage collected as long as a * reference to the iterator is hold. */ public Iterator iterator() { return Arrays.asList( toArray() ).iterator(); } /** * Returns a hash code value for the specified object. * Default implementation returns {@link Object#hashCode}. * Override to compute hash code in a different way. */ protected int hashCode( final Object object ) { return ( object != null ) ? object.hashCode() : 0; } /** * Check two objects for equality. This method should be overriden * if {@link #hashCode(Object)} has been overriden. */ protected boolean equals( final Object object1, final Object object2 ) { return object1 == object2 || ( object1 != null && object1.equals( object2 ) ); } }