blob: 01ad478a7630f6ee40987058a1eb4f933b3a1988 [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;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.Tapestry;
/**
* An implementation of the <b>Adaptor</b> pattern. The adaptor
* pattern allows new functionality to be assigned to an existing class.
* As implemented here, this is a smart lookup between
* a particular class (the class to be adapted, called
* the <em>subject class</em>) and some object instance
* that can provide the extra functionality (called the
* <em>adaptor</em>). The implementation of the adaptor is not relevant
* to the AdaptorRegistry class.
*
* <p>Adaptors are registered before they can be used; the registration maps a
* particular class to an adaptor instance. The adaptor instance will be used
* when the subject class matches the registered class, or the subject class
* inherits from the registered class.
*
* <p>This means that a search must be made that walks the inheritance tree
* (upwards from the subject class) to find a registered mapping.
*
* <p>In addition, adaptors can be registered against <em>interfaces</em>.
* Searching of interfaces occurs after searching of classes. The exact order is:
*
* <ul>
* <li>Search for the subject class, then each super-class of the subject class
* (excluding java.lang.Object)
* <li>Search interfaces, starting with interfaces implemented by the subject class,
* continuing with interfaces implemented by the super-classes, then
* interfaces extended by earlier interfaces (the exact order is a bit fuzzy)
* <li>Search for a match for java.lang.Object, if any
* </ul>
*
* <p>The first match terminates the search.
*
* <p>The AdaptorRegistry caches the results of search; a subsequent search for the
* same subject class will be resolved immediately.
*
* <p>AdaptorRegistry does a minor tweak of the "natural" inheritance.
* Normally, the parent class of an object array (i.e., <code>Foo[]</code>) is
* simply <code>Object</code>, even though you may assign
* <code>Foo[]</code> to a variable of type <code>Object[]</code>. AdaptorRegistry
* "fixes" this by searching for <code>Object[]</code> as if it was the superclass of
* any object array. This means that the search path for <code>Foo[]</code> is
* <code>Foo[]</code>, <code>Object[]</code>, then a couple of interfaces
* {@link java.lang.Cloneable}, {@link java.io.Serializable}, etc. that are\
* implicitily implemented by arrarys), and then, finally, <code>Object</code>
*
* <p>
* This tweak doesn't apply to scalar arrays, since scalar arrays may <em>not</em>
* be assigned to <code>Object[]</code>.
*
* <p>This class is thread safe.
*
*
* @version $Id$
* @author Howard Lewis Ship
*
**/
public class AdaptorRegistry
{
private static final Log LOG = LogFactory.getLog(AdaptorRegistry.class);
/**
* A Map of adaptor objects, keyed on registration Class.
*
**/
private Map registrations = new HashMap();
/**
* A Map of adaptor objects, keyed on subject Class.
*
**/
private Map cache = new HashMap();
/**
* Registers an adaptor for a registration class.
*
* @throws IllegalArgumentException if an adaptor has already
* been registered for the given class.
**/
public synchronized void register(Class registrationClass, Object adaptor)
{
if (registrations.containsKey(registrationClass))
throw new IllegalArgumentException(
Tapestry.format(
"AdaptorRegistry.duplicate-registration",
Tapestry.getClassName(registrationClass)));
registrations.put(registrationClass, adaptor);
if (LOG.isDebugEnabled())
LOG.debug("Registered " + adaptor + " for " + Tapestry.getClassName(registrationClass));
// Can't tell what is and isn't valid in the cache.
// Also, normally all registrations occur before any adaptors
// are searched for, so this is not a big deal.
cache.clear();
}
/**
* Gets the adaptor for the specified subjectClass.
*
* @throws IllegalArgumentException if no adaptor could be found.
*
**/
public synchronized Object getAdaptor(Class subjectClass)
{
Object result;
if (LOG.isDebugEnabled())
LOG.debug("Getting adaptor for class " + Tapestry.getClassName(subjectClass));
result = cache.get(subjectClass);
if (result != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Found " + result + " in cache");
return result;
}
result = searchForAdaptor(subjectClass);
// Record the result in the cache
cache.put(subjectClass, result);
if (LOG.isDebugEnabled())
LOG.debug("Found " + result);
return result;
}
/**
* Searches the registration Map for a match, based on inheritance.
*
* <p>Searches class inheritance first, then interfaces (in a rather vague order).
* Really should match the order from the JVM spec.
*
* <p>There's a degenerate case where we may check the same interface more than once:
* <ul>
* <li>Two interfaces, I1 and I2
* <li>Two classes, C1 and C2
* <li>I2 extends I1
* <li>C2 extends C1
* <li>C1 implements I1
* <li>C2 implements I2
* <li>The search will be: C2, C1, I2, I1, I1
* <li>I1 is searched twice, because C1 implements it, and I2 extends it
* <li>There are other such cases, but none of them cause infinite loops
* and most are rare (we could guard against it, but its relatively expensive).
* <li>Multiple checks only occur if we don't find a registration
* </ul>
*
* <p>
* This method is only called from a synchronized block, so it is
* implicitly synchronized.
*
**/
private Object searchForAdaptor(Class subjectClass)
{
LinkedList queue = null;
Object result = null;
if (LOG.isDebugEnabled())
LOG.debug("Searching for adaptor for class " + Tapestry.getClassName(subjectClass));
// Step one: work up through the class inheritance.
Class searchClass = subjectClass;
// Primitive types have null, not Object, as their parent
// class.
while (searchClass != Object.class && searchClass != null)
{
result = registrations.get(searchClass);
if (result != null)
return result;
// Not an exact match. If the search class
// implements any interfaces, add them to the queue.
Class[] interfaces = searchClass.getInterfaces();
int length = interfaces.length;
if (queue == null && length > 0)
queue = new LinkedList();
for (int i = 0; i < length; i++)
queue.addLast(interfaces[i]);
// Advance up to the next superclass
searchClass = getSuperclass(searchClass);
}
// Ok, the easy part failed, lets start searching
// interfaces.
if (queue != null)
{
while (!queue.isEmpty())
{
searchClass = (Class) queue.removeFirst();
result = registrations.get(searchClass);
if (result != null)
return result;
// Interfaces can extend other interfaces; add them
// to the queue.
Class[] interfaces = searchClass.getInterfaces();
int length = interfaces.length;
for (int i = 0; i < length; i++)
queue.addLast(interfaces[i]);
}
}
// Not a match on interface; our last gasp is to check
// for a registration for java.lang.Object
result = registrations.get(Object.class);
if (result != null)
return result;
// No match? That's rare ... and an error.
throw new IllegalArgumentException(
Tapestry.format(
"AdaptorRegistry.adaptor-not-found",
Tapestry.getClassName(subjectClass)));
}
/**
* Returns the superclass of the given class, with a single tweak: If the
* search class is an array class, and the component type is an object class
* (but not Object), then the simple Object array class is returned. This reflects
* the fact that an array of any class may be assignable to <code>Object[]</code>,
* even though the superclass of an array is always simply <code>Object</code>.
*
**/
private Class getSuperclass(Class searchClass)
{
if (searchClass.isArray())
{
Class componentType = searchClass.getComponentType();
if (!componentType.isPrimitive() && componentType != Object.class)
return Object[].class;
}
return searchClass.getSuperclass();
}
public synchronized String toString()
{
StringBuffer buffer = new StringBuffer();
buffer.append("AdaptorRegistry[");
Iterator i = registrations.entrySet().iterator();
boolean showSep = false;
while (i.hasNext())
{
if (showSep)
buffer.append(' ');
Map.Entry entry = (Map.Entry) i.next();
Class registeredClass = (Class) entry.getKey();
buffer.append(Tapestry.getClassName(registeredClass));
buffer.append("=");
buffer.append(entry.getValue());
showSep = true;
}
buffer.append("]");
return buffer.toString();
}
}