Java tutorial
// 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: AdaptorRegistry.java,v 1.8 2004/02/19 17:37:47 hlship Exp $ * @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(); } }