blob: 7d96ea2998f796cc7c51870be82855240f72b216 [file] [log] [blame]
// Copyright 2004 The Apache Software Foundation
//
// 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 org.apache.tapestry.util.pool;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.ApplicationRuntimeException;
import org.apache.tapestry.IMarkupWriter;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.util.AdaptorRegistry;
import org.apache.tapestry.util.ICleanable;
import org.apache.tapestry.util.IRenderDescription;
import org.apache.tapestry.util.JanitorThread;
/**
* A Pool is used to pool instances of a useful class. It uses
* keys, much like a {@link Map}, to identify a list of pooled objects.
* Retrieving an object from the Pool atomically removes it from the
* pool. It can then be stored again later. In this way, a single
* Pool instance can manage many different types of pooled objects,
* filed under different keys.
*
* <p>
* Unlike traditional Pools, this class does not create new instances of
* the objects it stores (with the exception of simple Java Beans,
* via {@link #retrieve(Class)}. The usage pattern is to retrieve an instance
* from the Pool, and if the instance is null, create a new instance.
*
* <p>The implementation of Pool is threadsafe.
*
* <p>Pool implements {@link ICleanable}, with a goal of
* only keeping pooled objects that have been needed within
* a recent time frame. A generational system is used, where each
* pooled object is assigned a generation count. {@link #executeCleanup}
* discards objects whose generation count is too old (outside of a
* {@link #getWindow() window}).
*
* <p>
* Objects in the pool can receive two notifications: one notification
* when they are {@link #store(Object, Object) stored} into the pool,
* and one when they are discarded from the pool.
*
* <p>
* Classes that implement {@link org.apache.tapestry.util.pool.IPoolable}
* receive notifications directly, as per the two methods
* of that interface.
*
* <p>
* Alternately, an adaptor for the other classes can be
* registerered (using {@link #registerAdaptor(Class, IPoolableAdaptor)}.
* The adaptor will be invoked to handle the notification when a
* pooled object is stored or discarded.
*
* @author Howard Lewis Ship
* @version $Id$
*
**/
public class Pool implements ICleanable, IRenderDescription
{
private static final Log LOG = LogFactory.getLog(Pool.class);
private AdaptorRegistry _adaptors = new AdaptorRegistry();
/**
* The generation, used to cull unused pooled items.
*
* @since 1.0.5
**/
private int _generation;
/**
* The generation window, used to identify which
* items should be culled.
*
* @since 1.0.5
**/
private int _window = 10;
/**
* The number of objects pooled.
*
**/
private int _pooledCount;
/**
* A map of PoolLists, keyed on an arbitrary object.
*
**/
private Map _map;
/**
* Creates a new Pool using the default map size. Creation of the map is deferred.
*
*
**/
public Pool()
{
this(true);
}
/**
* Creates a new Pool using the specified map size. The map is created immediately.
*
* @deprecated Use {@link #Pool()} instead.
*
**/
public Pool(int mapSize)
{
this(mapSize, true);
}
/**
* @param useSharedJanitor if true, then the Pool is added to
* the {@link JanitorThread#getSharedJanitorThread() shared janitor}.
*
* @since 1.0.5
*
**/
public Pool(boolean useSharedJanitor)
{
if (useSharedJanitor)
JanitorThread.getSharedJanitorThread().add(this);
registerAdaptors();
}
/**
* Standard constructor.
*
* @param mapSize initial size of the map.
* @param useSharedJanitor if true, then the Pool is added to
* the {@link JanitorThread#getSharedJanitorThread() shared janitor}.
*
* @since 1.0.5
* @deprecated Use {@link #Pool(boolean)} instead.
*
**/
public Pool(int mapSize, boolean useSharedJanitor)
{
this(useSharedJanitor);
_map = new HashMap(mapSize);
}
/**
* Returns the window used to cull pooled objects during a cleanup.
* The default is 10, which works out to about five minutes with
* a standard janitor (on a 30 second cycle).
*
* @since 1.0.5
*
**/
public int getWindow()
{
return _window;
}
/**
* Sets the window, or number of generations that an object may stay
* in the pool before being culled.
*
* @throws IllegalArgumentException if value is less than 1.
*
* @since 1.0.5
**/
public void setWindow(int value)
{
if (value < 1)
throw new IllegalArgumentException("Pool window may not be less than 1.");
_window = value;
}
/**
* Returns a previously pooled object with the given key, or null if no
* such object exists. Getting an object from a Pool removes it from the Pool,
* but it can later be re-added with {@link #store(Object,Object)}.
*
**/
public synchronized Object retrieve(Object key)
{
Object result = null;
if (_map == null)
_map = new HashMap();
PoolList list = (PoolList) _map.get(key);
if (list != null)
result = list.retrieve();
if (result != null)
_pooledCount--;
if (LOG.isDebugEnabled())
LOG.debug("Retrieved " + result + " from " + key);
return result;
}
/**
* Retrieves an instance of the named class. If no pooled
* instance is available, a new instance is created
* (using the no arguments constructor). Objects are
* pooled using their actual class as a key.
*
* <p>
* However, don't be fooled by false economies. Unless
* an object is very expensive to create, pooling is
* <em>more</em> expensive than simply instantiating temporary
* instances and letting the garbage collector deal with it
* (this is counter intuitive, but true). For example,
* this method was originally created to allow pooling
* of {@link StringBuffer}, but testing showed that it
* was a net defecit.
*
**/
public Object retrieve(Class objectClass)
{
Object result = retrieve((Object) objectClass);
if (result == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No instance of " + objectClass.getName() + " is available, instantiating one.");
try
{
result = objectClass.newInstance();
}
catch (Exception ex)
{
throw new ApplicationRuntimeException(
Tapestry.format("Pool.unable-to-instantiate-instance", objectClass.getName()),
ex);
}
}
return result;
}
/**
* Stores an object using its class as a key.
*
* @see #retrieve(Class)
*
**/
public void store(Object object)
{
store(object.getClass(), object);
}
/**
* Stores an object in the pool for later retrieval, resetting
* the object for storage within the pool.
*
**/
public synchronized void store(Object key, Object object)
{
getAdaptor(object).resetForPool(object);
if (_map == null)
_map = new HashMap();
PoolList list = (PoolList) _map.get(key);
if (list == null)
{
list = new PoolList(this);
_map.put(key, list);
}
int count = list.store(_generation, object);
_pooledCount++;
if (LOG.isDebugEnabled())
LOG.debug("Stored " + object + " into " + key + " (" + count + " pooled)");
}
/**
* Removes all previously pooled objects from this Pool.
*
**/
public synchronized void clear()
{
if (_map != null)
{
Iterator i = _map.values().iterator();
while (i.hasNext())
{
PoolList list = (PoolList) i.next();
list.discardAll();
}
_map.clear();
}
_pooledCount = 0;
if (LOG.isDebugEnabled())
LOG.debug("Cleared");
}
/**
* Returns the number of object pooled, the sum of the number
* of objects in pooled under each key.
*
* @since 1.0.2
**/
public synchronized int getPooledCount()
{
return _pooledCount;
}
/**
* Returns the number of keys within the pool.
*
* @since 1.0.2
**/
public synchronized int getKeyCount()
{
if (_map == null)
return 0;
return _map.size();
}
/**
* Peforms culling of unneeded pooled objects.
*
* @since 1.0.5
*
**/
public synchronized void executeCleanup()
{
if (_map == null)
return;
if (LOG.isDebugEnabled())
LOG.debug("Executing cleanup of " + this);
_generation++;
int oldestGeneration = _generation - _window;
if (oldestGeneration < 0)
return;
int oldCount = _pooledCount;
int culledKeys = 0;
// During the cleanup, we keep the entire instance synchronized
// (meaning other threads will block when trying to store
// or retrieved pooled objects). Fortunately, this
// should be pretty darn quick!
int newCount = 0;
Iterator i = _map.entrySet().iterator();
while (i.hasNext())
{
Map.Entry e = (Map.Entry) i.next();
PoolList list = (PoolList) e.getValue();
int count = list.cleanup(oldestGeneration);
if (count == 0)
{
i.remove();
culledKeys++;
}
else
newCount += count;
}
_pooledCount = newCount;
if (LOG.isDebugEnabled())
LOG.debug("Culled " + (oldCount - _pooledCount) + " pooled objects and " + culledKeys + " keys.");
}
public synchronized String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("generation", _generation);
builder.append("pooledCount", _pooledCount);
return builder.toString();
}
/** @since 1.0.6 **/
public synchronized void renderDescription(IMarkupWriter writer)
{
writer.begin("table");
writer.attribute("border", "1");
writer.println();
writer.begin("tr");
writer.begin("th");
writer.attribute("colspan", "2");
writer.print(toString());
writer.end();
writer.end();
writer.println();
if (_map != null)
{
Iterator i = _map.entrySet().iterator();
while (i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
PoolList list = (PoolList) entry.getValue();
writer.begin("tr");
writer.begin("td");
writer.print(entry.getKey().toString());
writer.end();
writer.begin("td");
writer.print(list.getPooledCount());
writer.end();
writer.end();
writer.println();
}
}
}
/**
* Invoked from the constructor to register the default set of
* {@link org.apache.tapestry.util.pool.IPoolableAdaptor}. Subclasses
* may override this to register a different set.
*
* <p>
* Registers:
* <ul>
* <li>{@link NullPoolableAdaptor} for class Object
* <li>{@link DefaultPoolableAdaptor} for interface {@link IPoolable}
* <li>{@link StringBufferAdaptor} for {@link StringBuffer}
* </ul>
*
* @since 3.0
*
**/
protected void registerAdaptors()
{
registerAdaptor(Object.class, new NullPoolableAdaptor());
registerAdaptor(IPoolable.class, new DefaultPoolableAdaptor());
registerAdaptor(StringBuffer.class, new StringBufferAdaptor());
}
/**
* Registers an adaptor for a particular class (or interface).
*
* @since 3.0
*
**/
public void registerAdaptor(Class registrationClass, IPoolableAdaptor adaptor)
{
_adaptors.register(registrationClass, adaptor);
}
/**
* Returns an adaptor appropriate to the object.
*
**/
public IPoolableAdaptor getAdaptor(Object object)
{
return (IPoolableAdaptor) _adaptors.getAdaptor(object.getClass());
}
}