/*
InvidPool.java
This class is intended to serve as an efficient, garbage-collecting
object cache for Invids on the Ganymede server.
Created: 7 January 2005
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2014
The University of Texas at Austin
Contact information
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.common;
import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;
/*------------------------------------------------------------------------------
class
InvidPool
------------------------------------------------------------------------------*/
/**
* <p>This InvidPool class is used by the Invid class to provide an
* Invid when given a short type number and an int object number. By
* using an InvidPool, the server will be able to reuse previously
* created Invids, much as the JVM can reuse interned strings.</p>
*
* <p>InvidPool uses SoftReferences to permit automatic clean-up of the
* pool when pooled Invids fall out of usage in the rest of the
* Ganymede heap.</p>
*/
public class InvidPool implements InvidAllocator {
/**
* The load factor used when none specified in constructor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private InvidSlot[] table;
private int floor;
private int size;
private int threshold;
private final float loadFactor;
/**
* <p>InvidSlot items are added to this queue when the garbage
* collector detects that the InvidSlot is no longer referenced by
* hard references in the Ganymede heap.</p>
*/
private final ReferenceQueue queue = new ReferenceQueue();
/* -- */
public InvidPool(int initialCapacity, float loadFactor)
{
if (initialCapacity < 0)
{
throw new IllegalArgumentException("Illegal Initial Capacity: " +
initialCapacity);
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
{
throw new IllegalArgumentException("Illegal Load factor: " +
loadFactor);
}
table = new InvidSlot[initialCapacity];
this.loadFactor = loadFactor;
this.floor = initialCapacity;
threshold = (int) (initialCapacity * loadFactor);
}
public InvidPool(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public InvidPool()
{
this(25301, DEFAULT_LOAD_FACTOR); // 25301 is a nice, biggish prime
}
/**
* <p>This method takes the identifying elements of an Invid to be
* found, and searches to find a suitable Invid object to return.
* If one cannot found, null should be returned, in which case
* {@link arlut.csd.ganymede.common.Invid#createInvid(short,int)
* createInvid} will synthesize and return a new one.</p>
*/
public synchronized Invid findInvid(Invid newInvid)
{
expungeStaleEntries();
InvidSlot s = table[(newInvid.hashCode() & 0x7FFFFFFF) % table.length];
while (s != null)
{
Invid storedInvid = (Invid) s.get();
if (newInvid.equals(storedInvid))
{
return storedInvid;
}
s = s.next;
}
return null;
}
/**
* <p>This method takes the invid given and places in whatever storage
* mechanism is appropriate, if any, for findInvid() to later draw
* upon.</p>
*/
public synchronized void storeInvid(Invid newInvid)
{
expungeStaleEntries();
int i = (newInvid.hashCode() & 0x7FFFFFFF) % table.length;
InvidSlot s = table[i];
while (s != null)
{
Invid storedInvid = (Invid) s.get();
if (newInvid.equals(storedInvid))
{
return; // already stored
}
s = s.next;
}
table[i] = new InvidSlot(newInvid, queue, table[i]);
if (++size >= threshold)
{
resize(table.length * 2 + 1);
}
}
public synchronized int size()
{
if (size == 0)
{
return 0;
}
expungeStaleEntries();
return size;
}
/**
* <p>This method iterates polls through the ReferenceQueue,
* identifying InvidSlots that reference Invids which are no longer
* hard-referenced in the Ganymede heap and removing them from the
* hash table, thus freeing space in our pool.</p>
*/
private void expungeStaleEntries()
{
InvidSlot s;
/* -- */
while ((s = (InvidSlot) queue.poll()) != null)
{
int i = (s.hashCode() & 0x7FFFFFFF) % table.length;
if (table[i] == s)
{
table[i] = s.next;
}
else
{
InvidSlot prev = table[i];
InvidSlot p = table[i].next;
while (p != null)
{
if (p == s)
{
prev.next = p.next;
p.next = null;
size--;
break;
}
prev = p;
p = p.next;
}
}
}
// if we've drastically shrunk, we'll collapse our table down
if (floor < size && size < (table.length / 4))
{
resize(size * 2 + 1);
}
}
private void resize(int newCapacity)
{
threshold = (int)(newCapacity * loadFactor);
InvidSlot newMap[] = new InvidSlot[newCapacity];
for (int i = 0; i < table.length; i++)
{
InvidSlot s = table[i];
while (s != null)
{
InvidSlot next = s.next;
int index = (s.hashCode() & 0x7FFFFFFF) % newCapacity;
s.next = newMap[index];
newMap[index] = s;
s = next;
}
}
this.table = newMap;
}
}
/*------------------------------------------------------------------------------
class
InvidSlot
------------------------------------------------------------------------------*/
/**
* <p>This (non-public) class is used in the {@link
* arlut.csd.ganymede.common.InvidPool} to softly reference Invids
* that we are storing for purposes of interning.</p>
*/
class InvidSlot extends SoftReference {
/**
* For linking collision buckets.
*/
InvidSlot next;
/**
* We have to remember the hash code for the referent Invid so that
* we can properly find the hash slot this InvidSlot would have been
* contained in before the reference was released.
*/
int save_hash;
/* -- */
InvidSlot(Invid item, ReferenceQueue queue, InvidSlot next)
{
super(item, queue);
this.save_hash = item.hashCode();
this.next = next;
}
public boolean equals(Object o)
{
if (o == null)
{
return false;
}
return (o.equals(get()));
}
public int hashCode()
{
return save_hash;
}
}