You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/11/18 01:28:42 UTC

svn commit: r476389 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/util/ test/java/org/apache/tapestry/internal/services/

Author: hlship
Date: Fri Nov 17 16:28:41 2006
New Revision: 476389

URL: http://svn.apache.org/viewvc?view=rev&rev=476389
Log:
Change the check for non-private instance variables so that it doesn't trigger for non-component classes inside component packages (for example, inner classes of components).

Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java?view=diff&rev=476389&r1=476388&r2=476389
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java Fri Nov 17 16:28:41 2006
@@ -12,1360 +12,1367 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
-import static java.lang.String.format;
-import static org.apache.tapestry.util.CollectionFactory.newList;
-import static org.apache.tapestry.util.CollectionFactory.newMap;
-import static org.apache.tapestry.util.CollectionFactory.newSet;
-import static org.apache.tapestry.util.Defense.notBlank;
-import static org.apache.tapestry.util.Defense.notNull;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Modifier;
-import java.util.Collections;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javassist.CannotCompileException;
-import javassist.ClassPool;
-import javassist.CtBehavior;
-import javassist.CtClass;
-import javassist.CtConstructor;
-import javassist.CtField;
-import javassist.CtMember;
-import javassist.CtMethod;
-import javassist.CtNewConstructor;
-import javassist.CtNewMethod;
-import javassist.NotFoundException;
-import javassist.expr.ExprEditor;
-import javassist.expr.FieldAccess;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.ComponentResources;
-import org.apache.tapestry.internal.InternalComponentResources;
-import org.apache.tapestry.internal.util.InternalUtils;
-import org.apache.tapestry.model.ComponentModel;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.services.MethodSignature;
-import org.apache.tapestry.services.TransformUtils;
-import org.apache.tapestry.util.CollectionFactory;
-import org.apache.tapestry.util.IdAllocator;
-
-/**
- * Implementation of the {@link org.apache.tapestry.internal.services.InternalClassTransformation}
- * interface.
- */
-public final class InternalClassTransformationImpl implements InternalClassTransformation
-{
-    private boolean _frozen;
-
-    private final CtClass _ctClass;
-
-    private final Log _log;
-
-    private final InternalClassTransformation _parentTransformation;
-
-    private ClassPool _classPool;
-
-    private final IdAllocator _idAllocator;
-
-    /** Map, keyed on InjectKey, of field name. */
-    private final Map<InjectionKey, String> _injectionCache = newMap();
-
-    /** Map from a field to the annotation objects for that field. */
-    private Map<String, List<Annotation>> _fieldAnnotations = newMap();
-
-    /** Used to identify fields that have been "claimed" by other annotations. */
-    private Map<String, Object> _claimedFields = newMap();
-
-    private Set<String> _addedFieldNames = newSet();
-
-    private Set<CtBehavior> _addedMethods = newSet();
-
-    // Cache of class annotations
-
-    private List<Annotation> _classAnnotations;
-
-    // Cache of method annotations
-
-    private Map<CtMethod, List<Annotation>> _methodAnnotations = newMap();
-
-    private Map<CtMethod, MethodSignature> _methodSignatures = newMap();
-
-    // Key is field name, value is expression used to replace read access
-
-    private Map<String, String> _fieldReadTransforms;
-
-    // Key is field name, value is expression used to replace read access
-    private Map<String, String> _fieldWriteTransforms;
-
-    /** Contains the assembled Javassist code for the class' default constructor. */
-    private StringBuilder _constructor = new StringBuilder();
-
-    private final List<ConstructorArg> _constructorArgs;
-
-    private final ComponentModel _componentModel;
-
-    private final String _resourcesFieldName;
-
-    private final StringBuilder _description = new StringBuilder();
-
-    private Formatter _formatter = new Formatter(_description);
-
-    private ClassLoader _loader;
-
-    /**
-     * This is a constructor for the root class, the class that directly contains the ComponentClass
-     * annotation.
-     */
-    public InternalClassTransformationImpl(CtClass ctClass, ClassLoader loader, Log log,
-            ComponentModel componentModel)
-    {
-        _ctClass = ctClass;
-        _classPool = _ctClass.getClassPool();
-        _loader = loader;
-        _parentTransformation = null;
-        _componentModel = componentModel;
-
-        _idAllocator = new IdAllocator();
-
-        _log = log;
-
-        preloadMemberNames();
-
-        verifyFields();
-
-        _constructorArgs = newList();
-        _constructor.append("{\n");
-
-        addImplementedInterface(Component.class);
-
-        _resourcesFieldName = addInjectedFieldUncached(
-                InternalComponentResources.class,
-                "resources",
-                null);
-
-        MethodSignature sig = new MethodSignature(Modifier.PUBLIC | Modifier.FINAL,
-                ComponentResources.class.getName(), "getComponentResources", null, null);
-
-        addMethod(sig, "return " + _resourcesFieldName + ";");
-    }
-
-    public InternalClassTransformationImpl(CtClass ctClass,
-            InternalClassTransformation parentTransformation, ClassLoader loader, Log log,
-            ComponentModel componentModel)
-    {
-        _ctClass = ctClass;
-        _classPool = _ctClass.getClassPool();
-        _loader = loader;
-        _log = log;
-        _parentTransformation = parentTransformation;
-        _componentModel = componentModel;
-
-        _resourcesFieldName = parentTransformation.getResourcesFieldName();
-
-        _idAllocator = parentTransformation.getIdAllocator();
-
-        preloadMemberNames();
-
-        verifyFields();
-
-        _constructorArgs = parentTransformation.getConstructorArgs();
-
-        int count = _constructorArgs.size();
-
-        // Build the call to the super-constructor.
-
-        _constructor.append("{ super(");
-
-        for (int i = 1; i <= count; i++)
-        {
-            if (i > 1)
-                _constructor.append(", ");
-
-            // $0 is implicitly self, so the 0-index ConstructorArg will be Javassisst
-            // pseudeo-variable $1, and so forth.
-
-            _constructor.append("$" + i);
-        }
-
-        _constructor.append(");\n");
-
-    }
-
-    private void freeze()
-    {
-        _frozen = true;
-
-        // Free up stuff we don't need after freezing.
-        // Everything else should be final.
-
-        _fieldAnnotations = null;
-        _claimedFields = null;
-        _addedFieldNames = null;
-        _addedMethods = null;
-        _classAnnotations = null;
-        _methodAnnotations = null;
-        _methodSignatures = null;
-        _fieldReadTransforms = null;
-        _fieldWriteTransforms = null;
-        _constructor = null;
-        _formatter = null;
-        _loader = null;
-        // _ctClass = null; -- needed by toString()
-        _classPool = null;
-    }
-
-    public String getResourcesFieldName()
-    {
-        return _resourcesFieldName;
-    }
-
-    /** Loads the names of all declared fields and methods into the idAllocator. */
-
-    private void preloadMemberNames()
-    {
-        addMemberNames(_ctClass.getDeclaredFields());
-        addMemberNames(_ctClass.getDeclaredMethods());
-    }
-
-    /** Verifies that all non-static fields are private. */
-
-    private void verifyFields()
-    {
-        List<String> names = newList();
-
-        for (CtField field : _ctClass.getDeclaredFields())
-        {
-            int modifiers = field.getModifiers();
-
-            // Fields must be either static or private.
-
-            if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers))
-                continue;
-
-            names.add(field.getName());
-        }
-
-        if (!names.isEmpty())
-            _log.error(ServicesMessages.nonPrivateFields(getClassName(), names));
-    }
-
-    private void addMemberNames(CtMember[] members)
-    {
-        for (CtMember member : members)
-        {
-            _idAllocator.allocateId(member.getName());
-        }
-    }
-
-    public <T extends Annotation> T getFieldAnnotation(String fieldName, Class<T> annotationClass)
-    {
-        failIfFrozen();
-
-        List<Annotation> annotations = findFieldAnnotations(fieldName);
-
-        return findAnnotationInList(annotationClass, annotations);
-    }
-
-    public <T extends Annotation> T getMethodAnnotation(MethodSignature signature,
-            Class<T> annotationClass)
-    {
-        failIfFrozen();
-
-        CtMethod method = findMethod(signature);
-
-        if (method == null)
-            throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
-                    _ctClass,
-                    signature));
-
-        List<Annotation> annotations = findMethodAnnotations(method);
-
-        return findAnnotationInList(annotationClass, annotations);
-    }
-
-    /**
-     * Searches an array of objects (that are really annotations instances) to find one that is of
-     * the correct type, which is returned.
-     * 
-     * @param <T>
-     * @param annotationClass
-     *            the annotation to search for
-     * @param annotations
-     *            the available annotations
-     * @return the matching annotation instance, or null if not found
-     */
-    private <T extends Annotation> T findAnnotationInList(Class<T> annotationClass,
-            List<Annotation> annotations)
-    {
-        for (Object annotation : annotations)
-        {
-            if (annotationClass.isInstance(annotation))
-                return annotationClass.cast(annotation);
-        }
-
-        return null;
-    }
-
-    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
-    {
-        return findAnnotationInList(annotationClass, getClassAnnotations());
-    }
-
-    private List<Annotation> findFieldAnnotations(String fieldName)
-    {
-        List<Annotation> annotations = _fieldAnnotations.get(fieldName);
-
-        if (annotations == null)
-        {
-            annotations = findAnnotationsForField(fieldName);
-            _fieldAnnotations.put(fieldName, annotations);
-        }
-
-        return annotations;
-    }
-
-    private List<Annotation> findMethodAnnotations(CtMethod method)
-    {
-        List<Annotation> annotations = _methodAnnotations.get(method);
-
-        if (annotations == null)
-        {
-            annotations = extractAnnotations(method);
-
-            _methodAnnotations.put(method, annotations);
-        }
-
-        return annotations;
-    }
-
-    private List<Annotation> findAnnotationsForField(String fieldName)
-    {
-        CtField field = findDeclaredCtField(fieldName);
-
-        return extractAnnotations(field);
-    }
-
-    private List<Annotation> extractAnnotations(CtMember member)
-    {
-        try
-        {
-            List<Annotation> result = newList();
-
-            addAnnotationsToList(result, member.getAnnotations());
-
-            return result;
-        }
-        catch (ClassNotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private void addAnnotationsToList(List<Annotation> list, Object[] annotations)
-    {
-        for (Object o : annotations)
-        {
-            Annotation a = (Annotation) o;
-            list.add(a);
-        }
-    }
-
-    private CtField findDeclaredCtField(String fieldName)
-    {
-        try
-        {
-            return _ctClass.getDeclaredField(fieldName);
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ServicesMessages.missingDeclaredField(_ctClass, fieldName),
-                    ex);
-        }
-    }
-
-    public String newMemberName(String suggested)
-    {
-        failIfFrozen();
-
-        String memberName = InternalUtils.createMemberName(notBlank(suggested, "suggested"));
-
-        return _idAllocator.allocateId(memberName);
-    }
-
-    public void addImplementedInterface(Class interfaceClass)
-    {
-        failIfFrozen();
-
-        String interfaceName = interfaceClass.getName();
-
-        try
-        {
-            CtClass ctInterface = _classPool.get(interfaceName);
-
-            if (classImplementsInterface(ctInterface))
-                return;
-
-            implementDefaultMethodsForInterface(ctInterface);
-
-            _ctClass.addInterface(ctInterface);
-
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-    }
-
-    /**
-     * Adds default implementations for the methods defined by the interface (and all of its
-     * super-interfaces). The implementations return null (or 0, or false, as appropriate to to the
-     * method type). There are a number of degenerate cases that are not covered properly: these are
-     * related to base interfaces that may be implemented by base classes.
-     * 
-     * @param ctInterface
-     * @throws NotFoundException
-     */
-    private void implementDefaultMethodsForInterface(CtClass ctInterface) throws NotFoundException
-    {
-        // java.lang.Object is the parent interface of interfaces
-
-        if (ctInterface.getName().equals(Object.class.getName()))
-            return;
-
-        for (CtMethod method : ctInterface.getDeclaredMethods())
-        {
-            addDefaultImplementation(method);
-        }
-
-        for (CtClass parent : ctInterface.getInterfaces())
-        {
-            implementDefaultMethodsForInterface(parent);
-        }
-    }
-
-    private void addDefaultImplementation(CtMethod method) throws NotFoundException
-    {
-        // Javassist has an oddity for interfaces: methods "inherited" from java.lang.Object show
-        // up as methods of the interface. We skip those and only consider the methods
-        // that are abstract.
-
-        if (!Modifier.isAbstract(method.getModifiers()))
-            return;
-
-        try
-        {
-            CtMethod newMethod = CtNewMethod.copy(method, _ctClass, null);
-
-            // Methods from interfaces are always public. We definitely
-            // need to change the modifiers of the method so that
-            // it is not abstract.
-
-            newMethod.setModifiers(Modifier.PUBLIC);
-
-            // Javassist will provide a minimal implementation for us (return null, false, 0,
-            // whatever).
-
-            newMethod.setBody(null);
-
-            _ctClass.addMethod(newMethod);
-
-            MethodSignature sig = getMethodSignature(newMethod);
-
-            addMethodToDescription("add default", sig, "<default>");
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new RuntimeException(ServicesMessages.errorAddingMethod(_ctClass, method
-                    .getName(), ex), ex);
-        }
-
-    }
-
-    /**
-     * Check to see if the target class (or any of its super classes) implements the provided
-     * interface. This is geared for simple interfaces (that don't extend other interfaces), thus if
-     * the class (or a base class) implement interface Y that extends interface X, we may not return
-     * true for interface X.
-     */
-
-    private boolean classImplementsInterface(CtClass ctInterface) throws NotFoundException
-    {
-
-        for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
-        {
-            for (CtClass anInterface : current.getInterfaces())
-            {
-                if (anInterface == ctInterface)
-                    return true;
-            }
-        }
-
-        return false;
-    }
-
-    public void claimField(String fieldName, Object tag)
-    {
-        notBlank(fieldName, "fieldName");
-
-        failIfFrozen();
-
-        Object existing = _claimedFields.get(fieldName);
-
-        if (existing != null)
-        {
-            String message = ServicesMessages.fieldAlreadyClaimed(
-                    fieldName,
-                    _ctClass,
-                    existing,
-                    tag);
-
-            throw new RuntimeException(message);
-        }
-
-        // TODO: Ensure that fieldName is a known field?
-
-        _claimedFields.put(fieldName, tag);
-    }
-
-    public void addMethod(MethodSignature signature, String methodBody)
-    {
-        failIfFrozen();
-
-        CtClass returnType = findCtClass(signature.getReturnType());
-        CtClass[] parameters = buildCtClassList(signature.getParameterTypes());
-        CtClass[] exceptions = buildCtClassList(signature.getExceptionTypes());
-
-        String action = "add";
-
-        try
-        {
-            CtMethod existing = _ctClass.getDeclaredMethod(signature.getMethodName(), parameters);
-
-            if (existing != null)
-            {
-                action = "replace";
-
-                _ctClass.removeMethod(existing);
-            }
-        }
-        catch (NotFoundException ex)
-        {
-            // That's ok. Kind of sloppy to rely on a thrown exception; wish getDeclaredMethod()
-            // would return null for
-            // that case. Alternately, we could maintain a set of the method signatures of declared
-            // or added methods.
-        }
-
-        try
-        {
-
-            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters,
-                    _ctClass);
-
-            // TODO: Check for duplicate method add
-
-            method.setModifiers(signature.getModifiers());
-
-            method.setBody(methodBody);
-            method.setExceptionTypes(exceptions);
-
-            _ctClass.addMethod(method);
-
-            _addedMethods.add(method);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(
-                    signature,
-                    methodBody,
-                    ex), methodBody, ex);
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        addMethodToDescription(action, signature, methodBody);
-    }
-
-    private CtClass[] buildCtClassList(String[] typeNames)
-    {
-        CtClass[] result = new CtClass[typeNames.length];
-
-        for (int i = 0; i < typeNames.length; i++)
-            result[i] = findCtClass(typeNames[i]);
-
-        return result;
-    }
-
-    private CtClass findCtClass(String type)
-    {
-        try
-        {
-            return _classPool.get(type);
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    public void extendMethod(MethodSignature methodSignature, String methodBody)
-    {
-        failIfFrozen();
-
-        CtMethod method = findMethod(methodSignature);
-
-        try
-        {
-            method.insertAfter(methodBody);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(
-                    methodSignature,
-                    methodBody,
-                    ex), methodBody, ex);
-        }
-
-        addMethodToDescription("extend", methodSignature, methodBody);
-
-        _addedMethods.add(method);
-    }
-
-    private void addMethodToDescription(String operation, MethodSignature methodSignature,
-            String methodBody)
-    {
-        _formatter.format("%s method: %s %s %s(", operation, Modifier.toString(methodSignature
-                .getModifiers()), methodSignature.getReturnType(), methodSignature.getMethodName());
-
-        String[] parameterTypes = methodSignature.getParameterTypes();
-        for (int i = 0; i < parameterTypes.length; i++)
-        {
-            if (i > 0)
-                _description.append(", ");
-
-            _formatter.format("%s $%d", parameterTypes[i], i + 1);
-        }
-
-        _description.append(")");
-
-        String[] exceptionTypes = methodSignature.getExceptionTypes();
-        for (int i = 0; i < exceptionTypes.length; i++)
-        {
-            if (i == 0)
-                _description.append("\n  throws ");
-            else
-                _description.append(", ");
-
-            _description.append(exceptionTypes[i]);
-        }
-
-        _formatter.format("\n%s\n\n", methodBody);
-    }
-
-    private CtMethod findMethod(MethodSignature methodSignature)
-    {
-        CtMethod method = findDeclaredMethod(methodSignature);
-
-        if (method != null)
-            return method;
-
-        CtMethod result = addOverrideOfSuperclassMethod(methodSignature);
-
-        if (result != null)
-            return result;
-
-        throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
-                _ctClass,
-                methodSignature));
-    }
-
-    private CtMethod findDeclaredMethod(MethodSignature methodSignature)
-    {
-        for (CtMethod method : _ctClass.getDeclaredMethods())
-        {
-            if (match(method, methodSignature))
-                return method;
-        }
-
-        return null;
-    }
-
-    private CtMethod addOverrideOfSuperclassMethod(MethodSignature methodSignature)
-    {
-        try
-        {
-            for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
-            {
-                for (CtMethod method : current.getDeclaredMethods())
-                {
-                    if (match(method, methodSignature))
-                    {
-                        // TODO: If the moethod is not overridable (i.e. private, or final)?
-                        // Perhaps we should limit it to just public methods.
-
-                        CtMethod newMethod = CtNewMethod.delegator(method, _ctClass);
-                        _ctClass.addMethod(newMethod);
-
-                        return newMethod;
-                    }
-                }
-            }
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        // Not found in a super-class.
-
-        return null;
-    }
-
-    private boolean match(CtMethod method, MethodSignature sig)
-    {
-        if (!sig.getMethodName().equals(method.getName()))
-            return false;
-
-        CtClass[] paramTypes;
-
-        try
-        {
-            paramTypes = method.getParameterTypes();
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        String[] sigTypes = sig.getParameterTypes();
-
-        int count = sigTypes.length;
-
-        if (paramTypes.length != count)
-            return false;
-
-        for (int i = 0; i < count; i++)
-        {
-            String paramType = paramTypes[i].getName();
-
-            if (!paramType.equals(sigTypes[i]))
-                return false;
-        }
-
-        // Ignore exceptions thrown and modifiers.
-        // TODO: Validate a match on return type?
-
-        return true;
-    }
-
-    public List<String> findFieldsWithAnnotation(Class<? extends Annotation> annotationClass)
-    {
-        failIfFrozen();
-
-        List<String> result = newList();
-
-        for (CtField field : _ctClass.getDeclaredFields())
-        {
-            if (!isInstanceField(field))
-                continue;
-
-            String fieldName = field.getName();
-
-            List<Annotation> annotations = findFieldAnnotations(fieldName);
-
-            // Check to see if any of the annotations are the right type, if
-            // so, add the field name to the result.
-
-            for (Object annotation : annotations)
-            {
-                if (annotationClass.isInstance(annotation))
-                {
-                    result.add(fieldName);
-                    break;
-                }
-            }
-        }
-
-        Collections.sort(result);
-
-        return result;
-    }
-
-    public List<MethodSignature> findMethodsWithAnnotation(
-            Class<? extends Annotation> annotationClass)
-    {
-        failIfFrozen();
-
-        List<MethodSignature> result = newList();
-
-        for (CtMethod method : _ctClass.getDeclaredMethods())
-        {
-            List<Annotation> annotations = findMethodAnnotations(method);
-
-            if (findAnnotationInList(annotationClass, annotations) != null)
-            {
-                MethodSignature sig = getMethodSignature(method);
-                result.add(sig);
-            }
-        }
-
-        Collections.sort(result);
-
-        return result;
-    }
-
-    private MethodSignature getMethodSignature(CtMethod method)
-    {
-        MethodSignature result = _methodSignatures.get(method);
-        if (result == null)
-        {
-            try
-            {
-                String type = method.getReturnType().getName();
-                String[] parameters = toTypeNames(method.getParameterTypes());
-                String[] exceptions = toTypeNames(method.getExceptionTypes());
-
-                result = new MethodSignature(method.getModifiers(), type, method.getName(),
-                        parameters, exceptions);
-
-                _methodSignatures.put(method, result);
-            }
-            catch (NotFoundException ex)
-            {
-                throw new RuntimeException(ex);
-            }
-        }
-
-        return result;
-    }
-
-    private String[] toTypeNames(CtClass[] types)
-    {
-        String[] result = new String[types.length];
-
-        for (int i = 0; i < types.length; i++)
-            result[i] = types[i].getName();
-
-        return result;
-    }
-
-    public String[] findUnclaimedFields()
-    {
-        failIfFrozen();
-
-        List<String> names = newList();
-
-        for (CtField field : _ctClass.getDeclaredFields())
-        {
-            if (!isInstanceField(field))
-                continue;
-
-            String name = field.getName();
-
-            if (_claimedFields.containsKey(name) || _addedFieldNames.contains(name))
-                continue;
-
-            // May need to add a filter to edit out explicitly added fields.
-
-            names.add(name);
-        }
-
-        Collections.sort(names);
-
-        return names.toArray(new String[names.size()]);
-    }
-
-    private boolean isInstanceField(CtField field)
-    {
-        int modifiers = field.getModifiers();
-
-        return Modifier.isPrivate(modifiers) && !Modifier.isStatic(modifiers);
-    }
-
-    public String getFieldType(String fieldName)
-    {
-        failIfFrozen();
-
-        CtClass type = getFieldCtType(fieldName);
-
-        return type.getName();
-    }
-
-    private CtClass getFieldCtType(String fieldName)
-    {
-        try
-        {
-            CtField field = _ctClass.getDeclaredField(fieldName);
-
-            return field.getType();
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    public String addField(int modifiers, String type, String suggestedName)
-    {
-        failIfFrozen();
-
-        String fieldName = newMemberName(suggestedName);
-
-        try
-        {
-            CtClass ctType = convertNameToCtType(type);
-
-            CtField field = new CtField(ctType, fieldName, _ctClass);
-            field.setModifiers(modifiers);
-
-            _ctClass.addField(field);
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        _formatter
-                .format("add field: %s %s %s;\n\n", Modifier.toString(modifiers), type, fieldName);
-
-        _addedFieldNames.add(fieldName);
-
-        return fieldName;
-    }
-
-    public String addInjectedField(Class type, String suggestedName, Object value)
-    {
-        notNull(type, "type");
-
-        failIfFrozen();
-
-        InjectionKey key = new InjectionKey(type, value);
-
-        String fieldName = searchForPreviousInjection(key);
-
-        if (fieldName != null)
-            return fieldName;
-
-        // TODO: Probably doesn't handle arrays and primitives.
-
-        fieldName = addInjectedFieldUncached(type, suggestedName, value);
-
-        // Remember the injection in-case this class, or a subclass, injects the value again.
-
-        _injectionCache.put(key, fieldName);
-
-        return fieldName;
-    }
-
-    /**
-     * This is split out from {@link #addInjectedField(Class, String, Object)} to handle a special
-     * case for the InternalComponentResources, which is null when "injected" (during the class
-     * transformation) and is only determined when a component is actually instantiated.
-     */
-    private String addInjectedFieldUncached(Class type, String suggestedName, Object value)
-    {
-        CtClass ctType;
-
-        try
-        {
-            ctType = _classPool.get(type.getName());
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        String fieldName = addField(
-                Modifier.PROTECTED | Modifier.FINAL,
-                type.getName(),
-                suggestedName);
-
-        addInjectToConstructor(fieldName, ctType, value);
-
-        return fieldName;
-    }
-
-    public String searchForPreviousInjection(InjectionKey key)
-    {
-        String result = _injectionCache.get(key);
-
-        if (result != null)
-            return result;
-
-        if (_parentTransformation != null)
-            return _parentTransformation.searchForPreviousInjection(key);
-
-        return null;
-    }
-
-    /**
-     * Adds a parameter to the constructor for the class; the parameter is used to initialize the
-     * value for a field.
-     * 
-     * @param fieldName
-     *            name of field to inject
-     * @param fieldType
-     *            Javassist type of the field (and corresponding parameter)
-     * @param value
-     *            the value to be injected (which will in unusual cases be null)
-     */
-    private void addInjectToConstructor(String fieldName, CtClass fieldType, Object value)
-    {
-        _constructorArgs.add(new ConstructorArg(fieldType, value));
-
-        _constructor.append(format("  %s = $%d;\n", fieldName, _constructorArgs.size()));
-    }
-
-    public void injectField(String fieldName, Object value)
-    {
-        notNull(fieldName, "fieldName");
-
-        failIfFrozen();
-
-        CtClass type = getFieldCtType(fieldName);
-
-        addInjectToConstructor(fieldName, type, value);
-
-        makeReadOnly(fieldName);
-    }
-
-    private CtClass convertNameToCtType(String type) throws NotFoundException
-    {
-        return _classPool.get(type);
-    }
-
-    public void finish()
-    {
-        failIfFrozen();
-
-        performFieldTransformations();
-
-        addConstructor();
-
-        freeze();
-    }
-
-    private void addConstructor()
-    {
-        String initializer = _idAllocator.allocateId("initializer");
-
-        try
-        {
-            CtConstructor defaultConstructor = _ctClass.getConstructor("()V");
-
-            CtMethod initializerMethod = defaultConstructor.toMethod(initializer, _ctClass);
-
-            _ctClass.addMethod(initializerMethod);
-        }
-        catch (Exception ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        _formatter.format("convert default constructor: %s();\n\n", initializer);
-
-        int count = _constructorArgs.size();
-
-        CtClass[] types = new CtClass[count];
-
-        for (int i = 0; i < count; i++)
-        {
-            ConstructorArg arg = _constructorArgs.get(i);
-
-            types[i] = arg.getType();
-        }
-
-        // Add a call to the initializer; the method converted fromt the classes default
-        // constructor.
-
-        _constructor.append("  ");
-        _constructor.append(initializer);
-
-        _constructor.append("();\n\n}");
-
-        String constructorBody = _constructor.toString();
-
-        try
-        {
-            CtConstructor cons = CtNewConstructor.make(types, null, constructorBody, _ctClass);
-            _ctClass.addConstructor(cons);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-
-        _formatter.format("add constructor: %s(", _ctClass.getName());
-
-        for (int i = 0; i < count; i++)
-        {
-            if (i > 0)
-                _description.append(", ");
-
-            _formatter.format("%s $%d", types[i].getName(), i + 1);
-        }
-
-        _formatter.format(")\n%s\n\n", constructorBody);
-    }
-
-    public Instantiator createInstantiator(Class componentClass)
-    {
-        String className = _ctClass.getName();
-
-        if (!className.equals(componentClass.getName()))
-            throw new IllegalArgumentException(ServicesMessages.incorrectClassForInstantiator(
-                    className,
-                    componentClass));
-
-        Object[] parameters = new Object[_constructorArgs.size()];
-
-        // Skip the first constructor argument, it's always a placeholder
-        // for the InternalComponentResources instance that's provided
-        // later.
-
-        for (int i = 1; i < _constructorArgs.size(); i++)
-        {
-            parameters[i] = _constructorArgs.get(i).getValue();
-        }
-
-        return new ReflectiveInstantiator(_componentModel, componentClass, parameters);
-    }
-
-    private void failIfFrozen()
-    {
-        if (_frozen)
-            throw new IllegalStateException("The ClassTransformation instance (for "
-                    + _ctClass.getName()
-                    + ") has completed all transformations and may not be further modified.");
-    }
-
-    private void failIfNotFrozen()
-    {
-        if (!_frozen)
-            throw new IllegalStateException("The ClassTransformation instance (for "
-                    + _ctClass.getName() + ") has not yet completed all transformations.");
-    }
-
-    public IdAllocator getIdAllocator()
-    {
-        failIfNotFrozen();
-
-        return _idAllocator;
-    }
-
-    public List<ConstructorArg> getConstructorArgs()
-    {
-        failIfNotFrozen();
-
-        return CollectionFactory.newList(_constructorArgs);
-    }
-
-    public List<Annotation> getClassAnnotations()
-    {
-        failIfFrozen();
-
-        if (_classAnnotations == null)
-            assembleClassAnnotations();
-
-        return _classAnnotations;
-    }
-
-    private void assembleClassAnnotations()
-    {
-        _classAnnotations = newList();
-
-        try
-        {
-            for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
-            {
-                addAnnotationsToList(_classAnnotations, current.getAnnotations());
-            }
-        }
-        catch (NotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-        catch (ClassNotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    @Override
-    public String toString()
-    {
-        StringBuilder builder = new StringBuilder("InternalClassTransformation[\n");
-
-        try
-        {
-            Formatter formatter = new Formatter(builder);
-
-            formatter.format(
-                    "%s %s extends %s",
-                    Modifier.toString(_ctClass.getModifiers()),
-                    _ctClass.getName(),
-                    _ctClass.getSuperclass().getName());
-
-            CtClass[] interfaces = _ctClass.getInterfaces();
-
-            for (int i = 0; i < interfaces.length; i++)
-            {
-                if (i == 0)
-                    builder.append("\n  implements ");
-                else
-                    builder.append(", ");
-
-                builder.append(interfaces[i].getName());
-            }
-
-            formatter.format("\n\n%s", _description.toString());
-        }
-        catch (NotFoundException ex)
-        {
-            builder.append(ex);
-        }
-
-        builder.append("]");
-
-        return builder.toString();
-    }
-
-    public void makeReadOnly(String fieldName)
-    {
-        String methodName = "_write_" + InternalUtils.stripMemberPrefix(fieldName);
-
-        String fieldType = getFieldType(fieldName);
-
-        MethodSignature sig = new MethodSignature(Modifier.PRIVATE, "void", methodName,
-                new String[]
-                { fieldType }, null);
-
-        String message = ServicesMessages.readOnlyField(_ctClass.getName(), fieldName);
-
-        String body = format("throw new java.lang.RuntimeException(\"%s\");", message);
-
-        addMethod(sig, body);
-
-        replaceWriteAccess(fieldName, methodName);
-    }
-
-    public void replaceReadAccess(String fieldName, String methodName)
-    {
-        String body = String.format("$_ = %s();", methodName);
-
-        if (_fieldReadTransforms == null)
-            _fieldReadTransforms = newMap();
-
-        // TODO: Collisions?
-
-        _fieldReadTransforms.put(fieldName, body);
-
-        _formatter.format("replace read %s: %s();\n\n", fieldName, methodName);
-    }
-
-    public void replaceWriteAccess(String fieldName, String methodName)
-    {
-        String body = String.format("%s($1);", methodName);
-
-        if (_fieldWriteTransforms == null)
-            _fieldWriteTransforms = newMap();
-
-        // TODO: Collisions?
-
-        _fieldWriteTransforms.put(fieldName, body);
-
-        _formatter.format("replace write %s: %s();\n\n", fieldName, methodName);
-    }
-
-    private void performFieldTransformations()
-    {
-        // If no field transformations have been requested, then we can save ourselves some
-        // trouble!
-
-        if (_fieldReadTransforms == null && _fieldWriteTransforms == null)
-            return;
-
-        // Provide empty maps here, to make the code in the inner class a tad
-        // easier.
-
-        if (_fieldReadTransforms == null)
-            _fieldReadTransforms = newMap();
-
-        if (_fieldWriteTransforms == null)
-            _fieldWriteTransforms = newMap();
-
-        ExprEditor editor = new ExprEditor()
-        {
-            @Override
-            public void edit(FieldAccess access) throws CannotCompileException
-            {
-                // Ignore any methods to were added as part of the transformation.
-                // If we reference the field there, we really mean the field.
-
-                if (_addedMethods.contains(access.where()))
-                    return;
-
-                Map<String, String> transformMap = access.isReader() ? _fieldReadTransforms
-                        : _fieldWriteTransforms;
-
-                String body = transformMap.get(access.getFieldName());
-
-                if (body != null)
-                    access.replace(body);
-            }
-        };
-
-        try
-        {
-            _ctClass.instrument(editor);
-        }
-        catch (CannotCompileException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    public Class toClass(String type)
-    {
-        failIfFrozen();
-
-        // No reason why this can't be allowed to work after freezing.
-
-        String finalType = TransformUtils.getWrapperTypeName(type);
-
-        try
-        {
-            return Class.forName(finalType, true, _loader);
-        }
-        catch (ClassNotFoundException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    public String getClassName()
-    {
-        return _ctClass.getName();
-    }
-
-    public Log getLog()
-    {
-        return _log;
-    }
-}
+package org.apache.tapestry.internal.services;
+
+import static java.lang.String.format;
+import static org.apache.tapestry.util.CollectionFactory.newList;
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+import static org.apache.tapestry.util.CollectionFactory.newSet;
+import static org.apache.tapestry.util.Defense.notBlank;
+import static org.apache.tapestry.util.Defense.notNull;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.CtConstructor;
+import javassist.CtField;
+import javassist.CtMember;
+import javassist.CtMethod;
+import javassist.CtNewConstructor;
+import javassist.CtNewMethod;
+import javassist.NotFoundException;
+import javassist.expr.ExprEditor;
+import javassist.expr.FieldAccess;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.model.ComponentModel;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.services.MethodSignature;
+import org.apache.tapestry.services.TransformUtils;
+import org.apache.tapestry.util.CollectionFactory;
+import org.apache.tapestry.util.IdAllocator;
+
+/**
+ * Implementation of the {@link org.apache.tapestry.internal.services.InternalClassTransformation}
+ * interface.
+ */
+public final class InternalClassTransformationImpl implements InternalClassTransformation
+{
+    private boolean _frozen;
+
+    private final CtClass _ctClass;
+
+    private final Log _log;
+
+    private final InternalClassTransformation _parentTransformation;
+
+    private ClassPool _classPool;
+
+    private final IdAllocator _idAllocator;
+
+    /** Map, keyed on InjectKey, of field name. */
+    private final Map<InjectionKey, String> _injectionCache = newMap();
+
+    /** Map from a field to the annotation objects for that field. */
+    private Map<String, List<Annotation>> _fieldAnnotations = newMap();
+
+    /** Used to identify fields that have been "claimed" by other annotations. */
+    private Map<String, Object> _claimedFields = newMap();
+
+    private Set<String> _addedFieldNames = newSet();
+
+    private Set<CtBehavior> _addedMethods = newSet();
+
+    // Cache of class annotations
+
+    private List<Annotation> _classAnnotations;
+
+    // Cache of method annotations
+
+    private Map<CtMethod, List<Annotation>> _methodAnnotations = newMap();
+
+    private Map<CtMethod, MethodSignature> _methodSignatures = newMap();
+
+    // Key is field name, value is expression used to replace read access
+
+    private Map<String, String> _fieldReadTransforms;
+
+    // Key is field name, value is expression used to replace read access
+    private Map<String, String> _fieldWriteTransforms;
+
+    /** Contains the assembled Javassist code for the class' default constructor. */
+    private StringBuilder _constructor = new StringBuilder();
+
+    private final List<ConstructorArg> _constructorArgs;
+
+    private final ComponentModel _componentModel;
+
+    private final String _resourcesFieldName;
+
+    private final StringBuilder _description = new StringBuilder();
+
+    private Formatter _formatter = new Formatter(_description);
+
+    private ClassLoader _loader;
+
+    /**
+     * This is a constructor for the root class, the class that directly contains the ComponentClass
+     * annotation.
+     */
+    public InternalClassTransformationImpl(CtClass ctClass, ClassLoader loader, Log log,
+            ComponentModel componentModel)
+    {
+        _ctClass = ctClass;
+        _classPool = _ctClass.getClassPool();
+        _loader = loader;
+        _parentTransformation = null;
+        _componentModel = componentModel;
+
+        _idAllocator = new IdAllocator();
+
+        _log = log;
+
+        preloadMemberNames();
+
+        _constructorArgs = newList();
+        _constructor.append("{\n");
+
+        addImplementedInterface(Component.class);
+
+        _resourcesFieldName = addInjectedFieldUncached(
+                InternalComponentResources.class,
+                "resources",
+                null);
+
+        MethodSignature sig = new MethodSignature(Modifier.PUBLIC | Modifier.FINAL,
+                ComponentResources.class.getName(), "getComponentResources", null, null);
+
+        addMethod(sig, "return " + _resourcesFieldName + ";");
+    }
+
+    public InternalClassTransformationImpl(CtClass ctClass,
+            InternalClassTransformation parentTransformation, ClassLoader loader, Log log,
+            ComponentModel componentModel)
+    {
+        _ctClass = ctClass;
+        _classPool = _ctClass.getClassPool();
+        _loader = loader;
+        _log = log;
+        _parentTransformation = parentTransformation;
+        _componentModel = componentModel;
+
+        _resourcesFieldName = parentTransformation.getResourcesFieldName();
+
+        _idAllocator = parentTransformation.getIdAllocator();
+
+        preloadMemberNames();
+
+        verifyFields();
+
+        _constructorArgs = parentTransformation.getConstructorArgs();
+
+        int count = _constructorArgs.size();
+
+        // Build the call to the super-constructor.
+
+        _constructor.append("{ super(");
+
+        for (int i = 1; i <= count; i++)
+        {
+            if (i > 1)
+                _constructor.append(", ");
+
+            // $0 is implicitly self, so the 0-index ConstructorArg will be Javassisst
+            // pseudeo-variable $1, and so forth.
+
+            _constructor.append("$" + i);
+        }
+
+        _constructor.append(");\n");
+
+    }
+
+    private void freeze()
+    {
+        _frozen = true;
+
+        // Free up stuff we don't need after freezing.
+        // Everything else should be final.
+
+        _fieldAnnotations = null;
+        _claimedFields = null;
+        _addedFieldNames = null;
+        _addedMethods = null;
+        _classAnnotations = null;
+        _methodAnnotations = null;
+        _methodSignatures = null;
+        _fieldReadTransforms = null;
+        _fieldWriteTransforms = null;
+        _constructor = null;
+        _formatter = null;
+        _loader = null;
+        // _ctClass = null; -- needed by toString()
+        _classPool = null;
+    }
+
+    public String getResourcesFieldName()
+    {
+        return _resourcesFieldName;
+    }
+
+    /** Loads the names of all declared fields and methods into the idAllocator. */
+
+    private void preloadMemberNames()
+    {
+        addMemberNames(_ctClass.getDeclaredFields());
+        addMemberNames(_ctClass.getDeclaredMethods());
+    }
+
+    public void verifyFields()
+    {
+        List<String> names = newList();
+
+        for (CtField field : _ctClass.getDeclaredFields())
+        {
+            String name = field.getName();
+
+            if (_addedFieldNames.contains(name))
+                continue;
+
+            int modifiers = field.getModifiers();
+
+            // Fields must be either static or private.
+
+            if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers))
+                continue;
+
+            names.add(name);
+        }
+
+        if (!names.isEmpty())
+        {
+            Collections.sort(names);
+
+            _log.error(ServicesMessages.nonPrivateFields(getClassName(), names));
+        }
+    }
+
+    private void addMemberNames(CtMember[] members)
+    {
+        for (CtMember member : members)
+        {
+            _idAllocator.allocateId(member.getName());
+        }
+    }
+
+    public <T extends Annotation> T getFieldAnnotation(String fieldName, Class<T> annotationClass)
+    {
+        failIfFrozen();
+
+        List<Annotation> annotations = findFieldAnnotations(fieldName);
+
+        return findAnnotationInList(annotationClass, annotations);
+    }
+
+    public <T extends Annotation> T getMethodAnnotation(MethodSignature signature,
+            Class<T> annotationClass)
+    {
+        failIfFrozen();
+
+        CtMethod method = findMethod(signature);
+
+        if (method == null)
+            throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
+                    _ctClass,
+                    signature));
+
+        List<Annotation> annotations = findMethodAnnotations(method);
+
+        return findAnnotationInList(annotationClass, annotations);
+    }
+
+    /**
+     * Searches an array of objects (that are really annotations instances) to find one that is of
+     * the correct type, which is returned.
+     * 
+     * @param <T>
+     * @param annotationClass
+     *            the annotation to search for
+     * @param annotations
+     *            the available annotations
+     * @return the matching annotation instance, or null if not found
+     */
+    private <T extends Annotation> T findAnnotationInList(Class<T> annotationClass,
+            List<Annotation> annotations)
+    {
+        for (Object annotation : annotations)
+        {
+            if (annotationClass.isInstance(annotation))
+                return annotationClass.cast(annotation);
+        }
+
+        return null;
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    {
+        return findAnnotationInList(annotationClass, getClassAnnotations());
+    }
+
+    private List<Annotation> findFieldAnnotations(String fieldName)
+    {
+        List<Annotation> annotations = _fieldAnnotations.get(fieldName);
+
+        if (annotations == null)
+        {
+            annotations = findAnnotationsForField(fieldName);
+            _fieldAnnotations.put(fieldName, annotations);
+        }
+
+        return annotations;
+    }
+
+    private List<Annotation> findMethodAnnotations(CtMethod method)
+    {
+        List<Annotation> annotations = _methodAnnotations.get(method);
+
+        if (annotations == null)
+        {
+            annotations = extractAnnotations(method);
+
+            _methodAnnotations.put(method, annotations);
+        }
+
+        return annotations;
+    }
+
+    private List<Annotation> findAnnotationsForField(String fieldName)
+    {
+        CtField field = findDeclaredCtField(fieldName);
+
+        return extractAnnotations(field);
+    }
+
+    private List<Annotation> extractAnnotations(CtMember member)
+    {
+        try
+        {
+            List<Annotation> result = newList();
+
+            addAnnotationsToList(result, member.getAnnotations());
+
+            return result;
+        }
+        catch (ClassNotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void addAnnotationsToList(List<Annotation> list, Object[] annotations)
+    {
+        for (Object o : annotations)
+        {
+            Annotation a = (Annotation) o;
+            list.add(a);
+        }
+    }
+
+    private CtField findDeclaredCtField(String fieldName)
+    {
+        try
+        {
+            return _ctClass.getDeclaredField(fieldName);
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ServicesMessages.missingDeclaredField(_ctClass, fieldName),
+                    ex);
+        }
+    }
+
+    public String newMemberName(String suggested)
+    {
+        failIfFrozen();
+
+        String memberName = InternalUtils.createMemberName(notBlank(suggested, "suggested"));
+
+        return _idAllocator.allocateId(memberName);
+    }
+
+    public void addImplementedInterface(Class interfaceClass)
+    {
+        failIfFrozen();
+
+        String interfaceName = interfaceClass.getName();
+
+        try
+        {
+            CtClass ctInterface = _classPool.get(interfaceName);
+
+            if (classImplementsInterface(ctInterface))
+                return;
+
+            implementDefaultMethodsForInterface(ctInterface);
+
+            _ctClass.addInterface(ctInterface);
+
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+    }
+
+    /**
+     * Adds default implementations for the methods defined by the interface (and all of its
+     * super-interfaces). The implementations return null (or 0, or false, as appropriate to to the
+     * method type). There are a number of degenerate cases that are not covered properly: these are
+     * related to base interfaces that may be implemented by base classes.
+     * 
+     * @param ctInterface
+     * @throws NotFoundException
+     */
+    private void implementDefaultMethodsForInterface(CtClass ctInterface) throws NotFoundException
+    {
+        // java.lang.Object is the parent interface of interfaces
+
+        if (ctInterface.getName().equals(Object.class.getName()))
+            return;
+
+        for (CtMethod method : ctInterface.getDeclaredMethods())
+        {
+            addDefaultImplementation(method);
+        }
+
+        for (CtClass parent : ctInterface.getInterfaces())
+        {
+            implementDefaultMethodsForInterface(parent);
+        }
+    }
+
+    private void addDefaultImplementation(CtMethod method) throws NotFoundException
+    {
+        // Javassist has an oddity for interfaces: methods "inherited" from java.lang.Object show
+        // up as methods of the interface. We skip those and only consider the methods
+        // that are abstract.
+
+        if (!Modifier.isAbstract(method.getModifiers()))
+            return;
+
+        try
+        {
+            CtMethod newMethod = CtNewMethod.copy(method, _ctClass, null);
+
+            // Methods from interfaces are always public. We definitely
+            // need to change the modifiers of the method so that
+            // it is not abstract.
+
+            newMethod.setModifiers(Modifier.PUBLIC);
+
+            // Javassist will provide a minimal implementation for us (return null, false, 0,
+            // whatever).
+
+            newMethod.setBody(null);
+
+            _ctClass.addMethod(newMethod);
+
+            MethodSignature sig = getMethodSignature(newMethod);
+
+            addMethodToDescription("add default", sig, "<default>");
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new RuntimeException(ServicesMessages.errorAddingMethod(_ctClass, method
+                    .getName(), ex), ex);
+        }
+
+    }
+
+    /**
+     * Check to see if the target class (or any of its super classes) implements the provided
+     * interface. This is geared for simple interfaces (that don't extend other interfaces), thus if
+     * the class (or a base class) implement interface Y that extends interface X, we may not return
+     * true for interface X.
+     */
+
+    private boolean classImplementsInterface(CtClass ctInterface) throws NotFoundException
+    {
+
+        for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
+        {
+            for (CtClass anInterface : current.getInterfaces())
+            {
+                if (anInterface == ctInterface)
+                    return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void claimField(String fieldName, Object tag)
+    {
+        notBlank(fieldName, "fieldName");
+
+        failIfFrozen();
+
+        Object existing = _claimedFields.get(fieldName);
+
+        if (existing != null)
+        {
+            String message = ServicesMessages.fieldAlreadyClaimed(
+                    fieldName,
+                    _ctClass,
+                    existing,
+                    tag);
+
+            throw new RuntimeException(message);
+        }
+
+        // TODO: Ensure that fieldName is a known field?
+
+        _claimedFields.put(fieldName, tag);
+    }
+
+    public void addMethod(MethodSignature signature, String methodBody)
+    {
+        failIfFrozen();
+
+        CtClass returnType = findCtClass(signature.getReturnType());
+        CtClass[] parameters = buildCtClassList(signature.getParameterTypes());
+        CtClass[] exceptions = buildCtClassList(signature.getExceptionTypes());
+
+        String action = "add";
+
+        try
+        {
+            CtMethod existing = _ctClass.getDeclaredMethod(signature.getMethodName(), parameters);
+
+            if (existing != null)
+            {
+                action = "replace";
+
+                _ctClass.removeMethod(existing);
+            }
+        }
+        catch (NotFoundException ex)
+        {
+            // That's ok. Kind of sloppy to rely on a thrown exception; wish getDeclaredMethod()
+            // would return null for
+            // that case. Alternately, we could maintain a set of the method signatures of declared
+            // or added methods.
+        }
+
+        try
+        {
+
+            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters,
+                    _ctClass);
+
+            // TODO: Check for duplicate method add
+
+            method.setModifiers(signature.getModifiers());
+
+            method.setBody(methodBody);
+            method.setExceptionTypes(exceptions);
+
+            _ctClass.addMethod(method);
+
+            _addedMethods.add(method);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new MethodCompileException(ServicesMessages.methodCompileError(
+                    signature,
+                    methodBody,
+                    ex), methodBody, ex);
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        addMethodToDescription(action, signature, methodBody);
+    }
+
+    private CtClass[] buildCtClassList(String[] typeNames)
+    {
+        CtClass[] result = new CtClass[typeNames.length];
+
+        for (int i = 0; i < typeNames.length; i++)
+            result[i] = findCtClass(typeNames[i]);
+
+        return result;
+    }
+
+    private CtClass findCtClass(String type)
+    {
+        try
+        {
+            return _classPool.get(type);
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public void extendMethod(MethodSignature methodSignature, String methodBody)
+    {
+        failIfFrozen();
+
+        CtMethod method = findMethod(methodSignature);
+
+        try
+        {
+            method.insertAfter(methodBody);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new MethodCompileException(ServicesMessages.methodCompileError(
+                    methodSignature,
+                    methodBody,
+                    ex), methodBody, ex);
+        }
+
+        addMethodToDescription("extend", methodSignature, methodBody);
+
+        _addedMethods.add(method);
+    }
+
+    private void addMethodToDescription(String operation, MethodSignature methodSignature,
+            String methodBody)
+    {
+        _formatter.format("%s method: %s %s %s(", operation, Modifier.toString(methodSignature
+                .getModifiers()), methodSignature.getReturnType(), methodSignature.getMethodName());
+
+        String[] parameterTypes = methodSignature.getParameterTypes();
+        for (int i = 0; i < parameterTypes.length; i++)
+        {
+            if (i > 0)
+                _description.append(", ");
+
+            _formatter.format("%s $%d", parameterTypes[i], i + 1);
+        }
+
+        _description.append(")");
+
+        String[] exceptionTypes = methodSignature.getExceptionTypes();
+        for (int i = 0; i < exceptionTypes.length; i++)
+        {
+            if (i == 0)
+                _description.append("\n  throws ");
+            else
+                _description.append(", ");
+
+            _description.append(exceptionTypes[i]);
+        }
+
+        _formatter.format("\n%s\n\n", methodBody);
+    }
+
+    private CtMethod findMethod(MethodSignature methodSignature)
+    {
+        CtMethod method = findDeclaredMethod(methodSignature);
+
+        if (method != null)
+            return method;
+
+        CtMethod result = addOverrideOfSuperclassMethod(methodSignature);
+
+        if (result != null)
+            return result;
+
+        throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
+                _ctClass,
+                methodSignature));
+    }
+
+    private CtMethod findDeclaredMethod(MethodSignature methodSignature)
+    {
+        for (CtMethod method : _ctClass.getDeclaredMethods())
+        {
+            if (match(method, methodSignature))
+                return method;
+        }
+
+        return null;
+    }
+
+    private CtMethod addOverrideOfSuperclassMethod(MethodSignature methodSignature)
+    {
+        try
+        {
+            for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
+            {
+                for (CtMethod method : current.getDeclaredMethods())
+                {
+                    if (match(method, methodSignature))
+                    {
+                        // TODO: If the moethod is not overridable (i.e. private, or final)?
+                        // Perhaps we should limit it to just public methods.
+
+                        CtMethod newMethod = CtNewMethod.delegator(method, _ctClass);
+                        _ctClass.addMethod(newMethod);
+
+                        return newMethod;
+                    }
+                }
+            }
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        // Not found in a super-class.
+
+        return null;
+    }
+
+    private boolean match(CtMethod method, MethodSignature sig)
+    {
+        if (!sig.getMethodName().equals(method.getName()))
+            return false;
+
+        CtClass[] paramTypes;
+
+        try
+        {
+            paramTypes = method.getParameterTypes();
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        String[] sigTypes = sig.getParameterTypes();
+
+        int count = sigTypes.length;
+
+        if (paramTypes.length != count)
+            return false;
+
+        for (int i = 0; i < count; i++)
+        {
+            String paramType = paramTypes[i].getName();
+
+            if (!paramType.equals(sigTypes[i]))
+                return false;
+        }
+
+        // Ignore exceptions thrown and modifiers.
+        // TODO: Validate a match on return type?
+
+        return true;
+    }
+
+    public List<String> findFieldsWithAnnotation(Class<? extends Annotation> annotationClass)
+    {
+        failIfFrozen();
+
+        List<String> result = newList();
+
+        for (CtField field : _ctClass.getDeclaredFields())
+        {
+            if (!isInstanceField(field))
+                continue;
+
+            String fieldName = field.getName();
+
+            List<Annotation> annotations = findFieldAnnotations(fieldName);
+
+            // Check to see if any of the annotations are the right type, if
+            // so, add the field name to the result.
+
+            for (Object annotation : annotations)
+            {
+                if (annotationClass.isInstance(annotation))
+                {
+                    result.add(fieldName);
+                    break;
+                }
+            }
+        }
+
+        Collections.sort(result);
+
+        return result;
+    }
+
+    public List<MethodSignature> findMethodsWithAnnotation(
+            Class<? extends Annotation> annotationClass)
+    {
+        failIfFrozen();
+
+        List<MethodSignature> result = newList();
+
+        for (CtMethod method : _ctClass.getDeclaredMethods())
+        {
+            List<Annotation> annotations = findMethodAnnotations(method);
+
+            if (findAnnotationInList(annotationClass, annotations) != null)
+            {
+                MethodSignature sig = getMethodSignature(method);
+                result.add(sig);
+            }
+        }
+
+        Collections.sort(result);
+
+        return result;
+    }
+
+    private MethodSignature getMethodSignature(CtMethod method)
+    {
+        MethodSignature result = _methodSignatures.get(method);
+        if (result == null)
+        {
+            try
+            {
+                String type = method.getReturnType().getName();
+                String[] parameters = toTypeNames(method.getParameterTypes());
+                String[] exceptions = toTypeNames(method.getExceptionTypes());
+
+                result = new MethodSignature(method.getModifiers(), type, method.getName(),
+                        parameters, exceptions);
+
+                _methodSignatures.put(method, result);
+            }
+            catch (NotFoundException ex)
+            {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        return result;
+    }
+
+    private String[] toTypeNames(CtClass[] types)
+    {
+        String[] result = new String[types.length];
+
+        for (int i = 0; i < types.length; i++)
+            result[i] = types[i].getName();
+
+        return result;
+    }
+
+    public String[] findUnclaimedFields()
+    {
+        failIfFrozen();
+
+        List<String> names = newList();
+
+        for (CtField field : _ctClass.getDeclaredFields())
+        {
+            if (!isInstanceField(field))
+                continue;
+
+            String name = field.getName();
+
+            if (_claimedFields.containsKey(name) || _addedFieldNames.contains(name))
+                continue;
+
+            // May need to add a filter to edit out explicitly added fields.
+
+            names.add(name);
+        }
+
+        Collections.sort(names);
+
+        return names.toArray(new String[names.size()]);
+    }
+
+    private boolean isInstanceField(CtField field)
+    {
+        int modifiers = field.getModifiers();
+
+        return Modifier.isPrivate(modifiers) && !Modifier.isStatic(modifiers);
+    }
+
+    public String getFieldType(String fieldName)
+    {
+        failIfFrozen();
+
+        CtClass type = getFieldCtType(fieldName);
+
+        return type.getName();
+    }
+
+    private CtClass getFieldCtType(String fieldName)
+    {
+        try
+        {
+            CtField field = _ctClass.getDeclaredField(fieldName);
+
+            return field.getType();
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public String addField(int modifiers, String type, String suggestedName)
+    {
+        failIfFrozen();
+
+        String fieldName = newMemberName(suggestedName);
+
+        try
+        {
+            CtClass ctType = convertNameToCtType(type);
+
+            CtField field = new CtField(ctType, fieldName, _ctClass);
+            field.setModifiers(modifiers);
+
+            _ctClass.addField(field);
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        _formatter
+                .format("add field: %s %s %s;\n\n", Modifier.toString(modifiers), type, fieldName);
+
+        _addedFieldNames.add(fieldName);
+
+        return fieldName;
+    }
+
+    public String addInjectedField(Class type, String suggestedName, Object value)
+    {
+        notNull(type, "type");
+
+        failIfFrozen();
+
+        InjectionKey key = new InjectionKey(type, value);
+
+        String fieldName = searchForPreviousInjection(key);
+
+        if (fieldName != null)
+            return fieldName;
+
+        // TODO: Probably doesn't handle arrays and primitives.
+
+        fieldName = addInjectedFieldUncached(type, suggestedName, value);
+
+        // Remember the injection in-case this class, or a subclass, injects the value again.
+
+        _injectionCache.put(key, fieldName);
+
+        return fieldName;
+    }
+
+    /**
+     * This is split out from {@link #addInjectedField(Class, String, Object)} to handle a special
+     * case for the InternalComponentResources, which is null when "injected" (during the class
+     * transformation) and is only determined when a component is actually instantiated.
+     */
+    private String addInjectedFieldUncached(Class type, String suggestedName, Object value)
+    {
+        CtClass ctType;
+
+        try
+        {
+            ctType = _classPool.get(type.getName());
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        String fieldName = addField(
+                Modifier.PROTECTED | Modifier.FINAL,
+                type.getName(),
+                suggestedName);
+
+        addInjectToConstructor(fieldName, ctType, value);
+
+        return fieldName;
+    }
+
+    public String searchForPreviousInjection(InjectionKey key)
+    {
+        String result = _injectionCache.get(key);
+
+        if (result != null)
+            return result;
+
+        if (_parentTransformation != null)
+            return _parentTransformation.searchForPreviousInjection(key);
+
+        return null;
+    }
+
+    /**
+     * Adds a parameter to the constructor for the class; the parameter is used to initialize the
+     * value for a field.
+     * 
+     * @param fieldName
+     *            name of field to inject
+     * @param fieldType
+     *            Javassist type of the field (and corresponding parameter)
+     * @param value
+     *            the value to be injected (which will in unusual cases be null)
+     */
+    private void addInjectToConstructor(String fieldName, CtClass fieldType, Object value)
+    {
+        _constructorArgs.add(new ConstructorArg(fieldType, value));
+
+        _constructor.append(format("  %s = $%d;\n", fieldName, _constructorArgs.size()));
+    }
+
+    public void injectField(String fieldName, Object value)
+    {
+        notNull(fieldName, "fieldName");
+
+        failIfFrozen();
+
+        CtClass type = getFieldCtType(fieldName);
+
+        addInjectToConstructor(fieldName, type, value);
+
+        makeReadOnly(fieldName);
+    }
+
+    private CtClass convertNameToCtType(String type) throws NotFoundException
+    {
+        return _classPool.get(type);
+    }
+
+    public void finish()
+    {
+        failIfFrozen();
+
+        performFieldTransformations();
+
+        addConstructor();
+
+        verifyFields();
+        
+        freeze();
+    }
+
+    private void addConstructor()
+    {
+        String initializer = _idAllocator.allocateId("initializer");
+
+        try
+        {
+            CtConstructor defaultConstructor = _ctClass.getConstructor("()V");
+
+            CtMethod initializerMethod = defaultConstructor.toMethod(initializer, _ctClass);
+
+            _ctClass.addMethod(initializerMethod);
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        _formatter.format("convert default constructor: %s();\n\n", initializer);
+
+        int count = _constructorArgs.size();
+
+        CtClass[] types = new CtClass[count];
+
+        for (int i = 0; i < count; i++)
+        {
+            ConstructorArg arg = _constructorArgs.get(i);
+
+            types[i] = arg.getType();
+        }
+
+        // Add a call to the initializer; the method converted fromt the classes default
+        // constructor.
+
+        _constructor.append("  ");
+        _constructor.append(initializer);
+
+        _constructor.append("();\n\n}");
+
+        String constructorBody = _constructor.toString();
+
+        try
+        {
+            CtConstructor cons = CtNewConstructor.make(types, null, constructorBody, _ctClass);
+            _ctClass.addConstructor(cons);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        _formatter.format("add constructor: %s(", _ctClass.getName());
+
+        for (int i = 0; i < count; i++)
+        {
+            if (i > 0)
+                _description.append(", ");
+
+            _formatter.format("%s $%d", types[i].getName(), i + 1);
+        }
+
+        _formatter.format(")\n%s\n\n", constructorBody);
+    }
+
+    public Instantiator createInstantiator(Class componentClass)
+    {
+        String className = _ctClass.getName();
+
+        if (!className.equals(componentClass.getName()))
+            throw new IllegalArgumentException(ServicesMessages.incorrectClassForInstantiator(
+                    className,
+                    componentClass));
+
+        Object[] parameters = new Object[_constructorArgs.size()];
+
+        // Skip the first constructor argument, it's always a placeholder
+        // for the InternalComponentResources instance that's provided
+        // later.
+
+        for (int i = 1; i < _constructorArgs.size(); i++)
+        {
+            parameters[i] = _constructorArgs.get(i).getValue();
+        }
+
+        return new ReflectiveInstantiator(_componentModel, componentClass, parameters);
+    }
+
+    private void failIfFrozen()
+    {
+        if (_frozen)
+            throw new IllegalStateException("The ClassTransformation instance (for "
+                    + _ctClass.getName()
+                    + ") has completed all transformations and may not be further modified.");
+    }
+
+    private void failIfNotFrozen()
+    {
+        if (!_frozen)
+            throw new IllegalStateException("The ClassTransformation instance (for "
+                    + _ctClass.getName() + ") has not yet completed all transformations.");
+    }
+
+    public IdAllocator getIdAllocator()
+    {
+        failIfNotFrozen();
+
+        return _idAllocator;
+    }
+
+    public List<ConstructorArg> getConstructorArgs()
+    {
+        failIfNotFrozen();
+
+        return CollectionFactory.newList(_constructorArgs);
+    }
+
+    public List<Annotation> getClassAnnotations()
+    {
+        failIfFrozen();
+
+        if (_classAnnotations == null)
+            assembleClassAnnotations();
+
+        return _classAnnotations;
+    }
+
+    private void assembleClassAnnotations()
+    {
+        _classAnnotations = newList();
+
+        try
+        {
+            for (CtClass current = _ctClass; current != null; current = current.getSuperclass())
+            {
+                addAnnotationsToList(_classAnnotations, current.getAnnotations());
+            }
+        }
+        catch (NotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+        catch (ClassNotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder("InternalClassTransformation[\n");
+
+        try
+        {
+            Formatter formatter = new Formatter(builder);
+
+            formatter.format(
+                    "%s %s extends %s",
+                    Modifier.toString(_ctClass.getModifiers()),
+                    _ctClass.getName(),
+                    _ctClass.getSuperclass().getName());
+
+            CtClass[] interfaces = _ctClass.getInterfaces();
+
+            for (int i = 0; i < interfaces.length; i++)
+            {
+                if (i == 0)
+                    builder.append("\n  implements ");
+                else
+                    builder.append(", ");
+
+                builder.append(interfaces[i].getName());
+            }
+
+            formatter.format("\n\n%s", _description.toString());
+        }
+        catch (NotFoundException ex)
+        {
+            builder.append(ex);
+        }
+
+        builder.append("]");
+
+        return builder.toString();
+    }
+
+    public void makeReadOnly(String fieldName)
+    {
+        String methodName = "_write_" + InternalUtils.stripMemberPrefix(fieldName);
+
+        String fieldType = getFieldType(fieldName);
+
+        MethodSignature sig = new MethodSignature(Modifier.PRIVATE, "void", methodName,
+                new String[]
+                { fieldType }, null);
+
+        String message = ServicesMessages.readOnlyField(_ctClass.getName(), fieldName);
+
+        String body = format("throw new java.lang.RuntimeException(\"%s\");", message);
+
+        addMethod(sig, body);
+
+        replaceWriteAccess(fieldName, methodName);
+    }
+
+    public void replaceReadAccess(String fieldName, String methodName)
+    {
+        String body = String.format("$_ = %s();", methodName);
+
+        if (_fieldReadTransforms == null)
+            _fieldReadTransforms = newMap();
+
+        // TODO: Collisions?
+
+        _fieldReadTransforms.put(fieldName, body);
+
+        _formatter.format("replace read %s: %s();\n\n", fieldName, methodName);
+    }
+
+    public void replaceWriteAccess(String fieldName, String methodName)
+    {
+        String body = String.format("%s($1);", methodName);
+
+        if (_fieldWriteTransforms == null)
+            _fieldWriteTransforms = newMap();
+
+        // TODO: Collisions?
+
+        _fieldWriteTransforms.put(fieldName, body);
+
+        _formatter.format("replace write %s: %s();\n\n", fieldName, methodName);
+    }
+
+    private void performFieldTransformations()
+    {
+        // If no field transformations have been requested, then we can save ourselves some
+        // trouble!
+
+        if (_fieldReadTransforms == null && _fieldWriteTransforms == null)
+            return;
+
+        // Provide empty maps here, to make the code in the inner class a tad
+        // easier.
+
+        if (_fieldReadTransforms == null)
+            _fieldReadTransforms = newMap();
+
+        if (_fieldWriteTransforms == null)
+            _fieldWriteTransforms = newMap();
+
+        ExprEditor editor = new ExprEditor()
+        {
+            @Override
+            public void edit(FieldAccess access) throws CannotCompileException
+            {
+                // Ignore any methods to were added as part of the transformation.
+                // If we reference the field there, we really mean the field.
+
+                if (_addedMethods.contains(access.where()))
+                    return;
+
+                Map<String, String> transformMap = access.isReader() ? _fieldReadTransforms
+                        : _fieldWriteTransforms;
+
+                String body = transformMap.get(access.getFieldName());
+
+                if (body != null)
+                    access.replace(body);
+            }
+        };
+
+        try
+        {
+            _ctClass.instrument(editor);
+        }
+        catch (CannotCompileException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public Class toClass(String type)
+    {
+        failIfFrozen();
+
+        // No reason why this can't be allowed to work after freezing.
+
+        String finalType = TransformUtils.getWrapperTypeName(type);
+
+        try
+        {
+            return Class.forName(finalType, true, _loader);
+        }
+        catch (ClassNotFoundException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public String getClassName()
+    {
+        return _ctClass.getName();
+    }
+
+    public Log getLog()
+    {
+        return _log;
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java?view=diff&rev=476389&r1=476388&r2=476389
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/util/URLChangeTracker.java Fri Nov 17 16:28:41 2006
@@ -12,90 +12,91 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.util;
-
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Map;
-
-import org.apache.tapestry.util.CollectionFactory;
-
-/**
- * Given a (growing) set of URLs, can periodically check to see if any of the underlying resources
- * has changed.
- * 
- * 
- */
-public class URLChangeTracker
-{
-    private final Map<URL, Long> _urlToTimestamp = CollectionFactory.newMap();
-
-    /**
-     * Stores a new URL into the tracker. The last modified timestamp for the URL is accessed and
-     * stored for later checking. May replacing an existing URL and timestamp.
-     * 
-     * @param url
-     *            of the resource to add
-     */
-
-    public synchronized void add(URL url)
-    {
-        long timestamp = readTimestamp(url);
-
-        _urlToTimestamp.put(url, timestamp);
-    }
-
-    /**
-     * Clears all URL and timestamp data stored in the tracker.
-     */
-    public synchronized void clear()
-    {
-        _urlToTimestamp.clear();
-    }
-
-    /**
-     * Re-acquires the last updated timestamp for each URL and returns true if any timestamp has
-     * changed.
-     */
-    public synchronized boolean containsChanges()
-    {
-        boolean result = false;
-
-        for (Map.Entry<URL, Long> entry : _urlToTimestamp.entrySet())
-        {
-            long newTimestamp = readTimestamp(entry.getKey());
-            long current = entry.getValue();
-
-            if (current == newTimestamp)
-                continue;
-
-            result = true;
-            entry.setValue(newTimestamp);
-        }
-
-        return result;
-    }
-
-    private long readTimestamp(URL url)
-    {
-        try
-        {
-            URLConnection connection = url.openConnection();
-
-            connection.connect();
-
-            long result = connection.getLastModified();
-
-            // System.out.println(url + " --> " + result);
-
-            return result;
-        }
-        catch (IOException ex)
-        {
-            throw new RuntimeException(UtilMessages.unableToReadLastModified(url, ex), ex);
-        }
-
-    }
-
-}
+package org.apache.tapestry.internal.util;
+
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Map;
+
+/**
+ * Given a (growing) set of URLs, can periodically check to see if any of the underlying resources
+ * has changed.
+ */
+public class URLChangeTracker
+{
+    private final Map<URL, Long> _urlToTimestamp = newMap();
+
+    /**
+     * Stores a new URL into the tracker. The last modified timestamp for the URL is accessed and
+     * stored for later checking. May replacing an existing URL and timestamp.
+     * 
+     * @param url
+     *            of the resource to add
+     */
+
+    public synchronized void add(URL url)
+    {
+        long timestamp = readTimestamp(url);
+
+        _urlToTimestamp.put(url, timestamp);
+    }
+
+    /**
+     * Clears all URL and timestamp data stored in the tracker.
+     */
+    public synchronized void clear()
+    {
+        _urlToTimestamp.clear();
+    }
+
+    /**
+     * Re-acquires the last updated timestamp for each URL and returns true if any timestamp has
+     * changed.
+     */
+    public synchronized boolean containsChanges()
+    {
+        boolean result = false;
+
+        for (Map.Entry<URL, Long> entry : _urlToTimestamp.entrySet())
+        {
+            long newTimestamp = readTimestamp(entry.getKey());
+            long current = entry.getValue();
+
+            if (current == newTimestamp)
+                continue;
+
+            result = true;
+            entry.setValue(newTimestamp);
+        }
+
+        return result;
+    }
+
+    private long readTimestamp(URL url)
+    {
+        try
+        {
+            URLConnection connection = url.openConnection();
+
+            connection.connect();
+
+            long result = connection.getLastModified();
+
+            // System.out.println(url + " --> " + result);
+
+            // So ... do you ever close the connection? So far, no problems using it like this.
+            // It must have a finalize() to close the connection if that's even necessary.
+
+            return result;
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(UtilMessages.unableToReadLastModified(url, ex), ex);
+        }
+
+    }
+
+}