Package org.apache.tapestry.internal.services

Source Code of org.apache.tapestry.internal.services.InternalClassTransformationImplTest

// Copyright 2006, 2007 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry.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.Field;
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.tapestry.annotations.Meta;
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.test.InternalBaseTestCase;
import org.apache.tapestry.internal.transform.InheritedAnnotation;
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.MethodIdentifier;
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.internal.services.PropertyAccessImpl;
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.MethodFilter;
import org.apache.tapestry.services.TransformMethodSignature;
import org.slf4j.Logger;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
* 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("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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

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

    @Test
    public void new_member_name_with_prefix() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        assertEquals(ct.newMemberName("prefix", "fred"), "_$prefix_fred");
        assertEquals(ct.newMemberName("prefix", "fred"), "_$prefix_fred_0");

        // Here we're exposing a bit of the internal algorithm, which strips
        // off '$' and '_' before tacking "_$" in front.

        assertEquals(ct.newMemberName("prefix", "_fred"), "_$prefix_fred_1");
        assertEquals(ct.newMemberName("prefix", "_$fred"), "_$prefix_fred_2");
        assertEquals(ct.newMemberName("prefix", "__$___$____$_fred"), "_$prefix_fred_3");

        verify();
    }

    private InternalClassTransformation createClassTransformation(Class targetClass, Logger logger)
            throws NotFoundException
    {
        CtClass ctClass = findCtClass(targetClass);

        return new InternalClassTransformationImpl(ctClass, _contextClassLoader, logger, null);
    }

    @Test
    public void find_annotation_on_unknown_field() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        Retain retain = ct.getFieldAnnotation("_annotatedField", Retain.class);

        assertNotNull(retain);

        verify();
    }

    @Test
    public void field_does_not_contain_requested_annotation() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        // 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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        List<String> fields = ct.findFieldsWithAnnotation(Retain.class);

        assertEquals(fields.size(), 1);
        assertEquals(fields.get(0), "_annotatedField");

        verify();
    }

    @Test
    public void get_field_modifiers() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(CheckFieldType.class, logger);

        assertEquals(ct.getFieldModifiers("_privateField"), Modifier.PRIVATE);
        assertEquals(ct.getFieldModifiers("_map"), Modifier.PRIVATE + Modifier.FINAL);
    }

    @Test
    public void get_field_exists() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(CheckFieldType.class, logger);

        assertTrue(ct.isField("_privateField"));
        assertFalse(ct.isField("_doesNotExist"));

        verify();
    }

    @Test
    public void find_fields_with_annotation_excludes_claimed_files() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        ct.claimField("_annotatedField", this);

        List<String> fields = ct.findFieldsWithAnnotation(Retain.class);

        assertTrue(fields.isEmpty());

        verify();
    }

    @Test
    public void no_fields_contain_requested_annotation() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        List<String> fields = ct.findFieldsWithAnnotation(Documented.class);

        assertTrue(fields.isEmpty());

        verify();
    }

    @Test
    public void claim_fields() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ClaimedFields.class, logger);

        List<String> unclaimed = ct.findUnclaimedFields();

        assertEquals(unclaimed, asList("_field1", "_field4", "_zzfield"));

        ct.claimField("_field4", "Fred");

        unclaimed = ct.findUnclaimedFields();

        assertEquals(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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ClaimedFields.class, logger);

        ct.addField(Modifier.PRIVATE, "int", "newField");

        List<String> unclaimed = ct.findUnclaimedFields();

        assertEquals(unclaimed, asList("_field1", "_field4", "_zzfield"));

        verify();
    }

    @Test
    public void find_class_annotations() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        Meta meta = ct.getAnnotation(Meta.class);

        assertNotNull(meta);

        // 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(InheritedAnnotation.class));

        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(
                ChildClassInheritsAnnotation.class,
                logger);

        InheritedAnnotation ia = ct.getAnnotation(InheritedAnnotation.class);

        // Javassist does not, but ClassTransformation patches around that.

        assertNotNull(ia);

        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 = mockInternalComponentResources();

        CtClass targetObjectCtClass = findCtClass(TargetObject.class);

        Logger logger = mockLogger();

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, 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 = mockInternalComponentResources();

        CtClass targetObjectCtClass = findCtClass(TargetObject.class);

        Logger logger = mockLogger();

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _loader, logger, 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, logger, 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 TransformMethodSignature(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);

        Logger logger = mockLogger();

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(ctClass,
                _contextClassLoader, logger, 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 = mockInternalComponentResources();

        CtClass targetObjectCtClass = findCtClass(TargetObject.class);

        Logger logger = mockLogger();

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, 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 = mockInternalComponentResources();

        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, 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 removed_fields_should_not_show_up_as_unclaimed() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(RemoveFieldBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, null);

        ct.removeField("_barney");

        assertEquals(ct.findUnclaimedFields(), asList("_fred"));

        verify();
    }

    @Test
    public void add_to_constructor() throws Exception
    {
        InternalComponentResources resources = mockInternalComponentResources();

        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, null);

        ct.extendConstructor("_value = \"from constructor\";");

        ct.finish();

        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);

        Object target = ct.createInstantiator(transformed).newInstance(resources);

        PropertyAccess access = new PropertyAccessImpl();

        assertEquals(access.get(target, "value"), "from constructor");

        verify();
    }

    @Test
    public void inject_field() throws Exception
    {
        InternalComponentResources resources = mockInternalComponentResources();

        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, 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 = mockInternalComponentResources();

        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(FieldAccessBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, 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;

        TransformMethodSignature readMethodSignature = new TransformMethodSignature(
                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;

        TransformMethodSignature writeMethodSignature = new TransformMethodSignature(
                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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, logger);

        List<TransformMethodSignature> 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 find_methods_using_filter() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        final ClassTransformation ct = createClassTransformation(AnnotatedPage.class, logger);

        // Duplicates, somewhat less efficiently, the logic in find_methods_with_annotation().

        MethodFilter filter = new MethodFilter()
        {
            public boolean accept(TransformMethodSignature signature)
            {
                return ct.getMethodAnnotation(signature, SetupRender.class) != null;
            }
        };

        List<TransformMethodSignature> l = ct.findMethods(filter);

        // 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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, logger);

        assertSame(ct.toClass("float"), Float.class);

        verify();
    }

    @Test
    public void to_class_with_object_type() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(AnnotatedPage.class, logger);

        assertSame(ct.toClass("java.util.Map"), Map.class);

        verify();
    }

    @Test
    public void non_private_fields_log_an_error() throws Exception
    {
        Logger logger = mockLogger();

        logger.error(ServicesMessages.nonPrivateFields(VisibilityBean.class.getName(), Arrays
                .asList("_$myPackagePrivate", "_$myProtected", "_$myPublic")));

        replay();

        InternalClassTransformation ct = createClassTransformation(VisibilityBean.class, logger);

        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
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(EventHandlerTarget.class, logger);

        OnEvent annotation = ct.getMethodAnnotation(
                new TransformMethodSignature("handler"),
                OnEvent.class);

        // Check that the attributes of the annotation match the expectation.

        assertEquals(annotation.value(), "fred");
        assertEquals(annotation.component(), "alpha");

        verify();
    }

    @Test
    public void find_annotation_in_unknown_method() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(ParentClass.class, logger);

        try
        {
            ct.getMethodAnnotation(new TransformMethodSignature("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();
    }
   
    @Test
    public void prefix_method() throws Exception {
        Logger logger = mockLogger();
        TransformMethodSignature sig = new TransformMethodSignature(Modifier.PUBLIC, "int", "getParentField", null, null);
       
        replay();
       
        InternalClassTransformation ct = createClassTransformation(ParentClass.class, logger);
        ct.prefixMethod(sig, "return 42;");
       
        String desc = ct.toString();
        assertTrue(desc.contains("prefix"));
        assertTrue(desc.contains("getParentField"));

        // fail if frozen
        ct.finish();
        try
        {
            ct.prefixMethod(sig, "return 0;");
            unreachable();
        }
        catch (IllegalStateException e) { }
       
       
        verify();
    }   

    @Test
    public void remove_field() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        CtClass targetObjectCtClass = findCtClass(FieldRemoval.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
                _contextClassLoader, logger, null);

        ct.removeField("_fieldToRemove");

        ct.finish();

        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);

        for (Field f : transformed.getDeclaredFields())
        {
            if (f.getName().equals("_fieldToRemove"))
                throw new AssertionError("_fieldToRemove still in transformed class.");
        }

        verify();
    }

    @Test
    public void get_method_identifier() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        ClassTransformation ct = createClassTransformation(MethodIdentifier.class, logger);

        List<TransformMethodSignature> sigs = ct.findMethodsWithAnnotation(OnEvent.class);

        assertEquals(sigs.size(), 1);

        TransformMethodSignature sig = sigs.get(0);

        assertEquals(
                ct.getMethodIdentifier(sig),
                "org.apache.tapestry.internal.transform.pages.MethodIdentifier.makeWaves(java.lang.String, int[]) (at MethodIdentifier.java:24)");

        verify();
    }
}
TOP

Related Classes of org.apache.tapestry.internal.services.InternalClassTransformationImplTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.