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();
+ }
+}