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 [2/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/

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java?view=diff&rev=476389&r1=476388&r2=476389
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java Fri Nov 17 16:28:41 2006
@@ -12,871 +12,874 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
-import static java.lang.Thread.currentThread;
-import static java.util.Arrays.asList;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Target;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtMethod;
-import javassist.Loader;
-import javassist.LoaderClassPath;
-import javassist.NotFoundException;
-
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.annotations.ComponentClass;
-import org.apache.tapestry.annotations.OnEvent;
-import org.apache.tapestry.annotations.Retain;
-import org.apache.tapestry.annotations.SetupRender;
-import org.apache.tapestry.internal.InternalComponentResources;
-import org.apache.tapestry.internal.ioc.services.PropertyAccessImpl;
-import org.apache.tapestry.internal.test.InternalBaseTestCase;
-import org.apache.tapestry.internal.transform.pages.AbstractFoo;
-import org.apache.tapestry.internal.transform.pages.BarImpl;
-import org.apache.tapestry.internal.transform.pages.BasicComponent;
-import org.apache.tapestry.internal.transform.pages.ChildClassInheritsAnnotation;
-import org.apache.tapestry.internal.transform.pages.ClaimedFields;
-import org.apache.tapestry.internal.transform.pages.EventHandlerTarget;
-import org.apache.tapestry.internal.transform.pages.ParentClass;
-import org.apache.tapestry.internal.transform.pages.TargetObject;
-import org.apache.tapestry.internal.transform.pages.TargetObjectSubclass;
-import org.apache.tapestry.ioc.services.PropertyAccess;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.runtime.ComponentResourcesAware;
-import org.apache.tapestry.services.ClassTransformation;
-import org.apache.tapestry.services.MethodSignature;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/**
- * <p>
- * The tests share a number of resources, and so are run sequentially.
- * 
- * 
- */
-@Test(sequential = true)
-public class InternalClassTransformationImplTest extends InternalBaseTestCase
-{
-    private static final String STRING_CLASS_NAME = "java.lang.String";
-
-    private ClassPool _classPool;
-
-    private final ClassLoader _contextClassLoader = currentThread().getContextClassLoader();
-
-    private Loader _loader;
-
-    private PropertyAccess _access;
-
-    @BeforeClass
-    public void setup_access()
-    {
-        _access = getService("tapestry.ioc.PropertyAccess", PropertyAccess.class);
-    }
-
-    @AfterClass
-    public void cleanup_access()
-    {
-        _access = null;
-    }
-
-    /**
-     * We need a new ClassPool for each individual test, since many of the tests will end up
-     * modifying one or more CtClass instances.
-     */
-    @BeforeMethod
-    public void setup_classpool()
-    {
-        _classPool = new ClassPool();
-
-        _loader = new Loader(_contextClassLoader, _classPool);
-
-        // This ensures that only the classes we explicitly access and modify
-        // are loaded by the new loader; everthing else comes out of the common
-        // context class loader, which prevents a lot of nasty class cast exceptions.
-
-        _loader.delegateLoadingOf("org.apache.tapestry.");
-
-        // Inside Maven Surefire, the system classpath is not sufficient to find all
-        // the necessary files.
-        _classPool.appendClassPath(new LoaderClassPath(_loader));
-    }
-
-    private CtClass findCtClass(Class targetClass) throws NotFoundException
-    {
-        return _classPool.get(targetClass.getName());
-    }
-
-    @Test
-    public void new_member_name() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        assertEquals(ct.newMemberName("fred"), "_$fred");
-        assertEquals(ct.newMemberName("fred"), "_$fred_0");
-
-        // Here we're exposing a bit of the internal algorithm, which strips
-        // off '$' and '_' before tacking "_$" in front.
-
-        assertEquals(ct.newMemberName("_fred"), "_$fred_1");
-        assertEquals(ct.newMemberName("_$fred"), "_$fred_2");
-        assertEquals(ct.newMemberName("__$___$____$_fred"), "_$fred_3");
-
-        // Here we're trying to force conflicts with existing declared
-        // fields and methods of the class.
-
-        assertEquals(ct.newMemberName("_parentField"), "_$parentField");
-        assertEquals(ct.newMemberName("conflictField"), "_$conflictField_0");
-        assertEquals(ct.newMemberName("conflictMethod"), "_$conflictMethod_0");
-
-        verify();
-    }
-
-    private InternalClassTransformation createClassTransformation(Class targetClass, Log log)
-            throws NotFoundException
-    {
-        CtClass ctClass = findCtClass(targetClass);
-
-        return new InternalClassTransformationImpl(ctClass, _contextClassLoader, log, null);
-    }
-
-    @Test
-    public void find_annotation_on_unknown_field() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        try
-        {
-            ct.getFieldAnnotation("unknownField", Retain.class);
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            assertEquals(
-                    ex.getMessage(),
-                    "Class org.apache.tapestry.internal.transform.pages.ParentClass does not contain a field named 'unknownField'.");
-        }
-
-        verify();
-    }
-
-    @Test
-    public void find_field_annotation() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        Retain retain = ct.getFieldAnnotation("_annotatedField", Retain.class);
-
-        assertNotNull(retain);
-
-        verify();
-    }
-
-    @Test
-    public void field_does_not_contain_requested_annotation() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        // Field with annotations, but not that annotation
-        assertNull(ct.getFieldAnnotation("_annotatedField", Override.class));
-
-        // Field with no annotations
-        assertNull(ct.getFieldAnnotation("_parentField", Override.class));
-
-        verify();
-    }
-
-    @Test
-    public void find_fields_with_annotation() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        List<String> fields = ct.findFieldsWithAnnotation(Retain.class);
-
-        assertEquals(fields.size(), 1);
-        assertEquals(fields.get(0), "_annotatedField");
-
-        verify();
-    }
-
-    @Test
-    public void no_fields_contain_requested_annotation() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        List<String> fields = ct.findFieldsWithAnnotation(Documented.class);
-
-        assertTrue(fields.isEmpty());
-
-        verify();
-    }
-
-    @Test
-    public void claim_fields() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ClaimedFields.class, log);
-
-        String[] unclaimed = ct.findUnclaimedFields();
-
-        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_field4", "_zzfield"));
-
-        ct.claimField("_field4", "Fred");
-
-        unclaimed = ct.findUnclaimedFields();
-
-        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_zzfield"));
-
-        try
-        {
-            ct.claimField("_field4", "Barney");
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            assertEquals(
-                    ex.getMessage(),
-                    "Field _field4 of class org.apache.tapestry.internal.transform.pages.ClaimedFields is already claimed by Fred and can not be claimed by Barney.");
-        }
-
-        verify();
-    }
-
-    @Test
-    public void added_fields_are_not_listed_as_unclaimed_fields() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ClaimedFields.class, log);
-
-        ct.addField(Modifier.PRIVATE, "int", "newField");
-
-        String[] unclaimed = ct.findUnclaimedFields();
-
-        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_field4", "_zzfield"));
-
-        verify();
-    }
-
-    @Test
-    public void find_class_annotations() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        ComponentClass cc = ct.getAnnotation(ComponentClass.class);
-
-        assertNotNull(cc);
-
-        // Try again (the annotations will be cached). Use an annotation
-        // that will not be present.
-
-        Target t = ct.getAnnotation(Target.class);
-
-        assertNull(t);
-
-        verify();
-    }
-
-    /**
-     * More a test of how Javassist works. Javassist does not honor the Inherited annotation for
-     * classes (this kind of makes sense, since it won't necessarily have the super-class in
-     * memory).
-     */
-    @Test
-    public void ensure_subclasses_inherit_parent_class_annotations() throws Exception
-    {
-        // The Java runtime does honor @Inherited
-        assertNotNull(ChildClassInheritsAnnotation.class.getAnnotation(ComponentClass.class));
-
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ChildClassInheritsAnnotation.class, log);
-
-        ComponentClass cc = ct.getAnnotation(ComponentClass.class);
-
-        // Javassist does not, but ClassTransformation patches around that.
-
-        assertNotNull(cc);
-
-        verify();
-    }
-
-    /**
-     * These tests are really to assert my understanding of Javassist's API. I guess we should keep
-     * them around to make sure that future versions of Javassist work the same as our expectations.
-     */
-    @Test
-    public void ensure_javassist_still_does_not_show_inherited_interfaces() throws Exception
-    {
-        CtClass ctClass = findCtClass(BarImpl.class);
-
-        CtClass[] interfaces = ctClass.getInterfaces();
-
-        // Just the interfaces implemented by this particular class, not
-        // inherited interfaces.
-
-        assertEquals(interfaces.length, 1);
-
-        assertEquals(interfaces[0].getName(), BarInterface.class.getName());
-
-        CtClass parentClass = ctClass.getSuperclass();
-
-        interfaces = parentClass.getInterfaces();
-
-        assertEquals(interfaces.length, 1);
-
-        assertEquals(interfaces[0].getName(), FooInterface.class.getName());
-    }
-
-    @Test
-    public void ensure_javassist_does_not_show_interface_methods_on_abstract_class()
-            throws Exception
-    {
-        CtClass ctClass = findCtClass(AbstractFoo.class);
-
-        CtClass[] interfaces = ctClass.getInterfaces();
-
-        assertEquals(interfaces.length, 1);
-
-        assertEquals(interfaces[0].getName(), FooInterface.class.getName());
-
-        // In some cases, Java reflection on an abstract class implementing an interface
-        // will show the interface methods as abstract methods on the class. This seems
-        // to vary from JVM to JVM. I believe Javassist is more consistent here.
-
-        CtMethod[] methods = ctClass.getDeclaredMethods();
-
-        assertEquals(methods.length, 0);
-    }
-
-    @Test
-    public void ensure_javassist_does_not_show_extended_interface_methods_on_interface()
-            throws Exception
-    {
-        CtClass ctClass = findCtClass(FooBarInterface.class);
-
-        // Just want to check that an interface that extends other interfaces
-        // doesn't show those other interface's methods.
-
-        CtMethod[] methods = ctClass.getDeclaredMethods();
-
-        assertEquals(methods.length, 0);
-    }
-
-    @Test
-    public void add_injected_field() throws Exception
-    {
-        InternalComponentResources resources = newInternalComponentResources();
-
-        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
-
-        Log log = newLog();
-
-        replay();
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _contextClassLoader, log, null);
-
-        // Default behavior is to add an injected field for the InternalComponentResources object,
-        // so we'll just check that.
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
-
-        Instantiator instantiator = ct.createInstantiator(transformed);
-
-        ComponentResourcesAware instance = instantiator.newInstance(resources);
-
-        assertSame(instance.getComponentResources(), resources);
-
-        verify();
-    }
-
-    @Test
-    public void add_injected_field_from_parent_transformation() throws Exception
-    {
-        final String value = "from the parent";
-
-        InternalComponentResources resources = newInternalComponentResources();
-
-        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
-
-        Log log = newLog();
-
-        replay();
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _loader, log, null);
-
-        String parentFieldName = ct.addInjectedField(String.class, "_value", value);
-
-        // Default behavior is to add an injected field for the InternalComponentResources object,
-        // so we'll just check that.
-
-        ct.finish();
-
-        // Instantiate the transformed base class, so that we can create a transformed
-        // subclass.
-
-        _classPool.toClass(targetObjectCtClass, _loader);
-
-        // Now lets work on the subclass
-
-        CtClass subclassCtClass = findCtClass(TargetObjectSubclass.class);
-
-        ct = new InternalClassTransformationImpl(subclassCtClass, ct, _loader, log, null);
-
-        String subclassFieldName = ct.addInjectedField(String.class, "_childValue", value);
-
-        // This is what proves it is cached.
-
-        assertEquals(subclassFieldName, parentFieldName);
-
-        // This proves the the field is protected and can be used in subclasses.
-
-        ct.addMethod(new MethodSignature(Modifier.PUBLIC, "java.lang.String", "getValue", null,
-                null), "return " + subclassFieldName + ";");
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(subclassCtClass, _loader);
-
-        Instantiator instantiator = ct.createInstantiator(transformed);
-
-        Object instance = instantiator.newInstance(resources);
-
-        Object actual = _access.get(instance, "value");
-
-        assertSame(actual, value);
-
-        verify();
-    }
-
-    @Test
-    public void wrong_instance_type_passed_to_create_instantiator() throws Exception
-    {
-        CtClass ctClass = findCtClass(BasicComponent.class);
-
-        Log log = newLog();
-
-        replay();
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(ctClass,
-                _contextClassLoader, log, null);
-
-        _classPool.toClass(ctClass, _loader);
-
-        try
-        {
-            ct.createInstantiator(Boolean.class);
-            unreachable();
-        }
-        catch (IllegalArgumentException ex)
-        {
-            assertEquals(ex.getMessage(), ServicesMessages.incorrectClassForInstantiator(
-                    BasicComponent.class.getName(),
-                    Boolean.class));
-        }
-
-        verify();
-    }
-
-    @Test
-    public void add_interface_to_class() throws Exception
-    {
-        InternalComponentResources resources = newInternalComponentResources();
-
-        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
-
-        Log log = newLog();
-
-        replay();
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _contextClassLoader, log, null);
-
-        ct.addImplementedInterface(FooInterface.class);
-        ct.addImplementedInterface(GetterMethodsInterface.class);
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
-
-        Class[] interfaces = transformed.getInterfaces();
-
-        assertEquals(interfaces, new Class[]
-        { Component.class, FooInterface.class, GetterMethodsInterface.class });
-
-        Object target = ct.createInstantiator(transformed).newInstance(resources);
-
-        FooInterface asFoo = (FooInterface) target;
-
-        asFoo.foo();
-
-        GetterMethodsInterface getters = (GetterMethodsInterface) target;
-
-        assertEquals(getters.getBoolean(), false);
-        assertEquals(getters.getByte(), (byte) 0);
-        assertEquals(getters.getShort(), (short) 0);
-        assertEquals(getters.getInt(), 0);
-        assertEquals(getters.getLong(), 0l);
-        assertEquals(getters.getFloat(), 0.0f);
-        assertEquals(getters.getDouble(), 0.0d);
-        assertNull(getters.getString());
-        assertNull(getters.getObjectArray());
-        assertNull(getters.getIntArray());
-
-        verify();
-    }
-
-    @Test
-    public void make_field_read_only() throws Exception
-    {
-        InternalComponentResources resources = newInternalComponentResources();
-
-        Log log = newLog();
-
-        replay();
-
-        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _contextClassLoader, log, null);
-
-        ct.makeReadOnly("_value");
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
-
-        Object target = ct.createInstantiator(transformed).newInstance(resources);
-
-        PropertyAccess access = new PropertyAccessImpl();
-
-        try
-        {
-            access.set(target, "value", "anything");
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            // The PropertyAccess layer adds a wrapper exception around the real one.
-
-            assertEquals(
-                    ex.getCause().getMessage(),
-                    "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
-        }
-
-        verify();
-    }
-
-    @Test
-    public void inject_field() throws Exception
-    {
-        InternalComponentResources resources = newInternalComponentResources();
-
-        Log log = newLog();
-
-        replay();
-
-        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _contextClassLoader, log, null);
-
-        ct.injectField("_value", "Tapestry");
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
-
-        Object target = ct.createInstantiator(transformed).newInstance(resources);
-
-        PropertyAccess access = new PropertyAccessImpl();
-
-        assertEquals(access.get(target, "value"), "Tapestry");
-
-        try
-        {
-            access.set(target, "value", "anything");
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            // The PropertyAccess layer adds a wrapper exception around the real one.
-
-            assertEquals(
-                    ex.getCause().getMessage(),
-                    "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
-        }
-
-        verify();
-    }
-
-    /**
-     * Tests the basic functionality of overriding read and write; also tests the case for multiple
-     * field read/field write substitions.
-     */
-    @Test
-    public void override_field_read_and_write() throws Exception
-    {
-        InternalComponentResources resources = newInternalComponentResources();
-
-        Log log = newLog();
-
-        replay();
-
-        CtClass targetObjectCtClass = findCtClass(FieldAccessBean.class);
-
-        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
-                _contextClassLoader, log, null);
-
-        replaceAccessToField(ct, "foo");
-        replaceAccessToField(ct, "bar");
-
-        // Stuff ...
-
-        ct.finish();
-
-        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
-
-        Object target = ct.createInstantiator(transformed).newInstance(resources);
-
-        // target is no longer assignable to FieldAccessBean; its a new class from a new class
-        // loader. So we use reflective access, which doesn't care about such things.
-
-        PropertyAccess access = new PropertyAccessImpl();
-
-        checkReplacedFieldAccess(access, target, "foo");
-        checkReplacedFieldAccess(access, target, "bar");
-
-        verify();
-    }
-
-    private void checkReplacedFieldAccess(PropertyAccess access, Object target, String propertyName)
-    {
-
-        try
-        {
-            access.get(target, propertyName);
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            // PropertyAccess adds a wrapper exception
-            assertEquals(ex.getCause().getMessage(), "read " + propertyName);
-        }
-
-        try
-        {
-            access.set(target, propertyName, "new value");
-            unreachable();
-        }
-        catch (RuntimeException ex)
-        {
-            // PropertyAccess adds a wrapper exception
-            assertEquals(ex.getCause().getMessage(), "write " + propertyName);
-        }
-    }
-
-    private void replaceAccessToField(InternalClassTransformation ct, String baseName)
-    {
-        String fieldName = "_" + baseName;
-        String readMethodName = "_read_" + baseName;
-
-        MethodSignature readMethodSignature = new MethodSignature(Modifier.PRIVATE,
-                STRING_CLASS_NAME, readMethodName, null, null);
-
-        ct.addMethod(readMethodSignature, String.format(
-                "throw new RuntimeException(\"read %s\");",
-                baseName));
-
-        ct.replaceReadAccess(fieldName, readMethodName);
-
-        String writeMethodName = "_write_" + baseName;
-
-        MethodSignature writeMethodSignature = new MethodSignature(Modifier.PRIVATE, "void",
-                writeMethodName, new String[]
-                { STRING_CLASS_NAME }, null);
-        ct.addMethod(writeMethodSignature, String.format(
-                "throw new RuntimeException(\"write %s\");",
-                baseName));
-
-        ct.replaceWriteAccess(fieldName, writeMethodName);
-    }
-
-    @Test
-    public void find_methods_with_annotation() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
-
-        List<MethodSignature> l = ct.findMethodsWithAnnotation(SetupRender.class);
-
-        // Check order
-
-        assertEquals(l.size(), 2);
-        assertEquals(l.get(0).toString(), "void beforeRender()");
-        assertEquals(l.get(1).toString(), "boolean earlyRender(org.apache.tapestry.MarkupWriter)");
-
-        // Check up on cacheing
-
-        assertEquals(ct.findMethodsWithAnnotation(SetupRender.class), l);
-
-        // Check up on no match.
-
-        assertTrue(ct.findFieldsWithAnnotation(Deprecated.class).isEmpty());
-
-        verify();
-    }
-
-    @Test
-    public void to_class_with_primitive_type() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
-
-        assertSame(ct.toClass("float"), Float.class);
-
-        verify();
-    }
-
-    @Test
-    public void to_class_with_object_type() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
-
-        assertSame(ct.toClass("java.util.Map"), Map.class);
-
-        verify();
-    }
-
-    @Test
-    public void non_private_fields_log_an_error() throws Exception
-    {
-        Log log = newLog();
-
-        log.error(ServicesMessages.nonPrivateFields(VisibilityBean.class.getName(), Arrays.asList(
-                "_$myPackagePrivate",
-                "_$myProtected",
-                "_$myPublic")));
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(VisibilityBean.class, log);
-
-        List<String> names = ct.findFieldsWithAnnotation(Retain.class);
-
-        // Only _myLong shows up, because its the only private field
-
-        assertEquals(names, Arrays.asList("_$myLong"));
-
-        // However, all the fields are "reserved" via the IdAllocator ...
-
-        assertEquals(ct.newMemberName("_$myLong"), "_$myLong_0");
-        assertEquals(ct.newMemberName("_$myStatic"), "_$myStatic_0");
-        assertEquals(ct.newMemberName("_$myProtected"), "_$myProtected_0");
-
-        verify();
-    }
-
-    @Test
-    public void find_annotation_in_method() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(EventHandlerTarget.class, log);
-
-        OnEvent annotation = ct.getMethodAnnotation(new MethodSignature("handler"), OnEvent.class);
-
-        // Check that the attributes of the annotation match the expectation.
-        
-        assertEquals(annotation.value(), new String[]
-        { "fred", "barney" });
-        assertEquals(annotation.component(), new String[]
-        { "alpha", "beta" });
-
-        verify();
-    }
-
-    @Test
-    public void find_annotation_in_unknown_method() throws Exception
-    {
-        Log log = newLog();
-
-        replay();
-
-        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
-
-        try
-        {
-            ct.getMethodAnnotation(new MethodSignature("foo"), OnEvent.class);
-            unreachable();
-        }
-        catch (IllegalArgumentException ex)
-        {
-            assertEquals(
-                    ex.getMessage(),
-                    "Class org.apache.tapestry.internal.transform.pages.ParentClass does not declare method 'public void foo()'.");
-        }
-
-        verify();
-    }
-}
+package org.apache.tapestry.internal.services;
+
+import static java.lang.Thread.currentThread;
+import static java.util.Arrays.asList;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Target;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtMethod;
+import javassist.Loader;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.OnEvent;
+import org.apache.tapestry.annotations.Retain;
+import org.apache.tapestry.annotations.SetupRender;
+import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.ioc.services.PropertyAccessImpl;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.internal.transform.pages.AbstractFoo;
+import org.apache.tapestry.internal.transform.pages.BarImpl;
+import org.apache.tapestry.internal.transform.pages.BasicComponent;
+import org.apache.tapestry.internal.transform.pages.ChildClassInheritsAnnotation;
+import org.apache.tapestry.internal.transform.pages.ClaimedFields;
+import org.apache.tapestry.internal.transform.pages.EventHandlerTarget;
+import org.apache.tapestry.internal.transform.pages.ParentClass;
+import org.apache.tapestry.internal.transform.pages.TargetObject;
+import org.apache.tapestry.internal.transform.pages.TargetObjectSubclass;
+import org.apache.tapestry.ioc.services.PropertyAccess;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.runtime.ComponentResourcesAware;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.MethodSignature;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * <p>
+ * The tests share a number of resources, and so are run sequentially.
+ */
+@Test(sequential = true)
+public class InternalClassTransformationImplTest extends InternalBaseTestCase
+{
+    private static final String STRING_CLASS_NAME = "java.lang.String";
+
+    private ClassPool _classPool;
+
+    private final ClassLoader _contextClassLoader = currentThread().getContextClassLoader();
+
+    private Loader _loader;
+
+    private PropertyAccess _access;
+
+    @BeforeClass
+    public void setup_access()
+    {
+        _access = getService("tapestry.ioc.PropertyAccess", PropertyAccess.class);
+    }
+
+    @AfterClass
+    public void cleanup_access()
+    {
+        _access = null;
+    }
+
+    /**
+     * We need a new ClassPool for each individual test, since many of the tests will end up
+     * modifying one or more CtClass instances.
+     */
+    @BeforeMethod
+    public void setup_classpool()
+    {
+        _classPool = new ClassPool();
+
+        _loader = new Loader(_contextClassLoader, _classPool);
+
+        // This ensures that only the classes we explicitly access and modify
+        // are loaded by the new loader; everthing else comes out of the common
+        // context class loader, which prevents a lot of nasty class cast exceptions.
+
+        _loader.delegateLoadingOf("org.apache.tapestry.");
+
+        // Inside Maven Surefire, the system classpath is not sufficient to find all
+        // the necessary files.
+        _classPool.appendClassPath(new LoaderClassPath(_loader));
+    }
+
+    private CtClass findCtClass(Class targetClass) throws NotFoundException
+    {
+        return _classPool.get(targetClass.getName());
+    }
+
+    @Test
+    public void new_member_name() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        assertEquals(ct.newMemberName("fred"), "_$fred");
+        assertEquals(ct.newMemberName("fred"), "_$fred_0");
+
+        // Here we're exposing a bit of the internal algorithm, which strips
+        // off '$' and '_' before tacking "_$" in front.
+
+        assertEquals(ct.newMemberName("_fred"), "_$fred_1");
+        assertEquals(ct.newMemberName("_$fred"), "_$fred_2");
+        assertEquals(ct.newMemberName("__$___$____$_fred"), "_$fred_3");
+
+        // Here we're trying to force conflicts with existing declared
+        // fields and methods of the class.
+
+        assertEquals(ct.newMemberName("_parentField"), "_$parentField");
+        assertEquals(ct.newMemberName("conflictField"), "_$conflictField_0");
+        assertEquals(ct.newMemberName("conflictMethod"), "_$conflictMethod_0");
+
+        verify();
+    }
+
+    private InternalClassTransformation createClassTransformation(Class targetClass, Log log)
+            throws NotFoundException
+    {
+        CtClass ctClass = findCtClass(targetClass);
+
+        return new InternalClassTransformationImpl(ctClass, _contextClassLoader, log, null);
+    }
+
+    @Test
+    public void find_annotation_on_unknown_field() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        try
+        {
+            ct.getFieldAnnotation("unknownField", Retain.class);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Class org.apache.tapestry.internal.transform.pages.ParentClass does not contain a field named 'unknownField'.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void find_field_annotation() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        Retain retain = ct.getFieldAnnotation("_annotatedField", Retain.class);
+
+        assertNotNull(retain);
+
+        verify();
+    }
+
+    @Test
+    public void field_does_not_contain_requested_annotation() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        // Field with annotations, but not that annotation
+        assertNull(ct.getFieldAnnotation("_annotatedField", Override.class));
+
+        // Field with no annotations
+        assertNull(ct.getFieldAnnotation("_parentField", Override.class));
+
+        verify();
+    }
+
+    @Test
+    public void find_fields_with_annotation() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        List<String> fields = ct.findFieldsWithAnnotation(Retain.class);
+
+        assertEquals(fields.size(), 1);
+        assertEquals(fields.get(0), "_annotatedField");
+
+        verify();
+    }
+
+    @Test
+    public void no_fields_contain_requested_annotation() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        List<String> fields = ct.findFieldsWithAnnotation(Documented.class);
+
+        assertTrue(fields.isEmpty());
+
+        verify();
+    }
+
+    @Test
+    public void claim_fields() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ClaimedFields.class, log);
+
+        String[] unclaimed = ct.findUnclaimedFields();
+
+        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_field4", "_zzfield"));
+
+        ct.claimField("_field4", "Fred");
+
+        unclaimed = ct.findUnclaimedFields();
+
+        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_zzfield"));
+
+        try
+        {
+            ct.claimField("_field4", "Barney");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Field _field4 of class org.apache.tapestry.internal.transform.pages.ClaimedFields is already claimed by Fred and can not be claimed by Barney.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void added_fields_are_not_listed_as_unclaimed_fields() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ClaimedFields.class, log);
+
+        ct.addField(Modifier.PRIVATE, "int", "newField");
+
+        String[] unclaimed = ct.findUnclaimedFields();
+
+        assertEquals(Arrays.asList(unclaimed), asList("_field1", "_field4", "_zzfield"));
+
+        verify();
+    }
+
+    @Test
+    public void find_class_annotations() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        ComponentClass cc = ct.getAnnotation(ComponentClass.class);
+
+        assertNotNull(cc);
+
+        // Try again (the annotations will be cached). Use an annotation
+        // that will not be present.
+
+        Target t = ct.getAnnotation(Target.class);
+
+        assertNull(t);
+
+        verify();
+    }
+
+    /**
+     * More a test of how Javassist works. Javassist does not honor the Inherited annotation for
+     * classes (this kind of makes sense, since it won't necessarily have the super-class in
+     * memory).
+     */
+    @Test
+    public void ensure_subclasses_inherit_parent_class_annotations() throws Exception
+    {
+        // The Java runtime does honor @Inherited
+        assertNotNull(ChildClassInheritsAnnotation.class.getAnnotation(ComponentClass.class));
+
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ChildClassInheritsAnnotation.class, log);
+
+        ComponentClass cc = ct.getAnnotation(ComponentClass.class);
+
+        // Javassist does not, but ClassTransformation patches around that.
+
+        assertNotNull(cc);
+
+        verify();
+    }
+
+    /**
+     * These tests are really to assert my understanding of Javassist's API. I guess we should keep
+     * them around to make sure that future versions of Javassist work the same as our expectations.
+     */
+    @Test
+    public void ensure_javassist_still_does_not_show_inherited_interfaces() throws Exception
+    {
+        CtClass ctClass = findCtClass(BarImpl.class);
+
+        CtClass[] interfaces = ctClass.getInterfaces();
+
+        // Just the interfaces implemented by this particular class, not
+        // inherited interfaces.
+
+        assertEquals(interfaces.length, 1);
+
+        assertEquals(interfaces[0].getName(), BarInterface.class.getName());
+
+        CtClass parentClass = ctClass.getSuperclass();
+
+        interfaces = parentClass.getInterfaces();
+
+        assertEquals(interfaces.length, 1);
+
+        assertEquals(interfaces[0].getName(), FooInterface.class.getName());
+    }
+
+    @Test
+    public void ensure_javassist_does_not_show_interface_methods_on_abstract_class()
+            throws Exception
+    {
+        CtClass ctClass = findCtClass(AbstractFoo.class);
+
+        CtClass[] interfaces = ctClass.getInterfaces();
+
+        assertEquals(interfaces.length, 1);
+
+        assertEquals(interfaces[0].getName(), FooInterface.class.getName());
+
+        // In some cases, Java reflection on an abstract class implementing an interface
+        // will show the interface methods as abstract methods on the class. This seems
+        // to vary from JVM to JVM. I believe Javassist is more consistent here.
+
+        CtMethod[] methods = ctClass.getDeclaredMethods();
+
+        assertEquals(methods.length, 0);
+    }
+
+    @Test
+    public void ensure_javassist_does_not_show_extended_interface_methods_on_interface()
+            throws Exception
+    {
+        CtClass ctClass = findCtClass(FooBarInterface.class);
+
+        // Just want to check that an interface that extends other interfaces
+        // doesn't show those other interface's methods.
+
+        CtMethod[] methods = ctClass.getDeclaredMethods();
+
+        assertEquals(methods.length, 0);
+    }
+
+    @Test
+    public void add_injected_field() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
+
+        Log log = newLog();
+
+        replay();
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        // Default behavior is to add an injected field for the InternalComponentResources object,
+        // so we'll just check that.
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Instantiator instantiator = ct.createInstantiator(transformed);
+
+        ComponentResourcesAware instance = instantiator.newInstance(resources);
+
+        assertSame(instance.getComponentResources(), resources);
+
+        verify();
+    }
+
+    @Test
+    public void add_injected_field_from_parent_transformation() throws Exception
+    {
+        final String value = "from the parent";
+
+        InternalComponentResources resources = newInternalComponentResources();
+
+        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
+
+        Log log = newLog();
+
+        replay();
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _loader, log, null);
+
+        String parentFieldName = ct.addInjectedField(String.class, "_value", value);
+
+        // Default behavior is to add an injected field for the InternalComponentResources object,
+        // so we'll just check that.
+
+        ct.finish();
+
+        // Instantiate the transformed base class, so that we can create a transformed
+        // subclass.
+
+        _classPool.toClass(targetObjectCtClass, _loader);
+
+        // Now lets work on the subclass
+
+        CtClass subclassCtClass = findCtClass(TargetObjectSubclass.class);
+
+        ct = new InternalClassTransformationImpl(subclassCtClass, ct, _loader, log, null);
+
+        String subclassFieldName = ct.addInjectedField(String.class, "_childValue", value);
+
+        // This is what proves it is cached.
+
+        assertEquals(subclassFieldName, parentFieldName);
+
+        // This proves the the field is protected and can be used in subclasses.
+
+        ct.addMethod(new MethodSignature(Modifier.PUBLIC, "java.lang.String", "getValue", null,
+                null), "return " + subclassFieldName + ";");
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(subclassCtClass, _loader);
+
+        Instantiator instantiator = ct.createInstantiator(transformed);
+
+        Object instance = instantiator.newInstance(resources);
+
+        Object actual = _access.get(instance, "value");
+
+        assertSame(actual, value);
+
+        verify();
+    }
+
+    @Test
+    public void wrong_instance_type_passed_to_create_instantiator() throws Exception
+    {
+        CtClass ctClass = findCtClass(BasicComponent.class);
+
+        Log log = newLog();
+
+        replay();
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(ctClass,
+                _contextClassLoader, log, null);
+
+        _classPool.toClass(ctClass, _loader);
+
+        try
+        {
+            ct.createInstantiator(Boolean.class);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(ex.getMessage(), ServicesMessages.incorrectClassForInstantiator(
+                    BasicComponent.class.getName(),
+                    Boolean.class));
+        }
+
+        verify();
+    }
+
+    @Test
+    public void add_interface_to_class() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        CtClass targetObjectCtClass = findCtClass(TargetObject.class);
+
+        Log log = newLog();
+
+        replay();
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        ct.addImplementedInterface(FooInterface.class);
+        ct.addImplementedInterface(GetterMethodsInterface.class);
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Class[] interfaces = transformed.getInterfaces();
+
+        assertEquals(interfaces, new Class[]
+        { Component.class, FooInterface.class, GetterMethodsInterface.class });
+
+        Object target = ct.createInstantiator(transformed).newInstance(resources);
+
+        FooInterface asFoo = (FooInterface) target;
+
+        asFoo.foo();
+
+        GetterMethodsInterface getters = (GetterMethodsInterface) target;
+
+        assertEquals(getters.getBoolean(), false);
+        assertEquals(getters.getByte(), (byte) 0);
+        assertEquals(getters.getShort(), (short) 0);
+        assertEquals(getters.getInt(), 0);
+        assertEquals(getters.getLong(), 0l);
+        assertEquals(getters.getFloat(), 0.0f);
+        assertEquals(getters.getDouble(), 0.0d);
+        assertNull(getters.getString());
+        assertNull(getters.getObjectArray());
+        assertNull(getters.getIntArray());
+
+        verify();
+    }
+
+    @Test
+    public void make_field_read_only() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        Log log = newLog();
+
+        replay();
+
+        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        ct.makeReadOnly("_value");
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Object target = ct.createInstantiator(transformed).newInstance(resources);
+
+        PropertyAccess access = new PropertyAccessImpl();
+
+        try
+        {
+            access.set(target, "value", "anything");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            // The PropertyAccess layer adds a wrapper exception around the real one.
+
+            assertEquals(
+                    ex.getCause().getMessage(),
+                    "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void inject_field() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        Log log = newLog();
+
+        replay();
+
+        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        ct.injectField("_value", "Tapestry");
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Object target = ct.createInstantiator(transformed).newInstance(resources);
+
+        PropertyAccess access = new PropertyAccessImpl();
+
+        assertEquals(access.get(target, "value"), "Tapestry");
+
+        try
+        {
+            access.set(target, "value", "anything");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            // The PropertyAccess layer adds a wrapper exception around the real one.
+
+            assertEquals(
+                    ex.getCause().getMessage(),
+                    "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
+        }
+
+        verify();
+    }
+
+    /**
+     * Tests the basic functionality of overriding read and write; also tests the case for multiple
+     * field read/field write substitions.
+     */
+    @Test
+    public void override_field_read_and_write() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        Log log = newLog();
+
+        replay();
+
+        CtClass targetObjectCtClass = findCtClass(FieldAccessBean.class);
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        replaceAccessToField(ct, "foo");
+        replaceAccessToField(ct, "bar");
+
+        // Stuff ...
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Object target = ct.createInstantiator(transformed).newInstance(resources);
+
+        // target is no longer assignable to FieldAccessBean; its a new class from a new class
+        // loader. So we use reflective access, which doesn't care about such things.
+
+        PropertyAccess access = new PropertyAccessImpl();
+
+        checkReplacedFieldAccess(access, target, "foo");
+        checkReplacedFieldAccess(access, target, "bar");
+
+        verify();
+    }
+
+    private void checkReplacedFieldAccess(PropertyAccess access, Object target, String propertyName)
+    {
+
+        try
+        {
+            access.get(target, propertyName);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            // PropertyAccess adds a wrapper exception
+            assertEquals(ex.getCause().getMessage(), "read " + propertyName);
+        }
+
+        try
+        {
+            access.set(target, propertyName, "new value");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            // PropertyAccess adds a wrapper exception
+            assertEquals(ex.getCause().getMessage(), "write " + propertyName);
+        }
+    }
+
+    private void replaceAccessToField(InternalClassTransformation ct, String baseName)
+    {
+        String fieldName = "_" + baseName;
+        String readMethodName = "_read_" + baseName;
+
+        MethodSignature readMethodSignature = new MethodSignature(Modifier.PRIVATE,
+                STRING_CLASS_NAME, readMethodName, null, null);
+
+        ct.addMethod(readMethodSignature, String.format(
+                "throw new RuntimeException(\"read %s\");",
+                baseName));
+
+        ct.replaceReadAccess(fieldName, readMethodName);
+
+        String writeMethodName = "_write_" + baseName;
+
+        MethodSignature writeMethodSignature = new MethodSignature(Modifier.PRIVATE, "void",
+                writeMethodName, new String[]
+                { STRING_CLASS_NAME }, null);
+        ct.addMethod(writeMethodSignature, String.format(
+                "throw new RuntimeException(\"write %s\");",
+                baseName));
+
+        ct.replaceWriteAccess(fieldName, writeMethodName);
+    }
+
+    @Test
+    public void find_methods_with_annotation() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
+
+        List<MethodSignature> l = ct.findMethodsWithAnnotation(SetupRender.class);
+
+        // Check order
+
+        assertEquals(l.size(), 2);
+        assertEquals(l.get(0).toString(), "void beforeRender()");
+        assertEquals(l.get(1).toString(), "boolean earlyRender(org.apache.tapestry.MarkupWriter)");
+
+        // Check up on cacheing
+
+        assertEquals(ct.findMethodsWithAnnotation(SetupRender.class), l);
+
+        // Check up on no match.
+
+        assertTrue(ct.findFieldsWithAnnotation(Deprecated.class).isEmpty());
+
+        verify();
+    }
+
+    @Test
+    public void to_class_with_primitive_type() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
+
+        assertSame(ct.toClass("float"), Float.class);
+
+        verify();
+    }
+
+    @Test
+    public void to_class_with_object_type() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, log);
+
+        assertSame(ct.toClass("java.util.Map"), Map.class);
+
+        verify();
+    }
+
+    @Test
+    public void non_private_fields_log_an_error() throws Exception
+    {
+        Log log = newLog();
+
+        log.error(ServicesMessages.nonPrivateFields(VisibilityBean.class.getName(), Arrays.asList(
+                "_$myPackagePrivate",
+                "_$myProtected",
+                "_$myPublic")));
+
+        replay();
+
+        InternalClassTransformation ct = createClassTransformation(VisibilityBean.class, log);
+
+        List<String> names = ct.findFieldsWithAnnotation(Retain.class);
+
+        // Only _myLong shows up, because its the only private field
+
+        assertEquals(names, Arrays.asList("_$myLong"));
+
+        // However, all the fields are "reserved" via the IdAllocator ...
+
+        assertEquals(ct.newMemberName("_$myLong"), "_$myLong_0");
+        assertEquals(ct.newMemberName("_$myStatic"), "_$myStatic_0");
+        assertEquals(ct.newMemberName("_$myProtected"), "_$myProtected_0");
+
+        // The check for non-private fields has been moved from the ICTI constructor to the finish
+        // method.
+
+        ct.finish();
+
+        verify();
+    }
+
+    @Test
+    public void find_annotation_in_method() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(EventHandlerTarget.class, log);
+
+        OnEvent annotation = ct.getMethodAnnotation(new MethodSignature("handler"), OnEvent.class);
+
+        // Check that the attributes of the annotation match the expectation.
+
+        assertEquals(annotation.value(), new String[]
+        { "fred", "barney" });
+        assertEquals(annotation.component(), new String[]
+        { "alpha", "beta" });
+
+        verify();
+    }
+
+    @Test
+    public void find_annotation_in_unknown_method() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        try
+        {
+            ct.getMethodAnnotation(new MethodSignature("foo"), OnEvent.class);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Class org.apache.tapestry.internal.transform.pages.ParentClass does not declare method 'public void foo()'.");
+        }
+
+        verify();
+    }
+}