Package org.apache.tapestry5.internal.services

Source Code of org.apache.tapestry5.internal.services.InternalClassTransformationImplTest$ProcessStringAndInteger

// Copyright 2006, 2007, 2008, 2010, 2011 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.tapestry5.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.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import javassist.CtClass;
import javassist.CtMethod;
import javassist.Loader;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

import org.apache.tapestry5.annotations.Meta;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Retain;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.internal.InternalComponentResources;
import org.apache.tapestry5.internal.model.MutableComponentModelImpl;
import org.apache.tapestry5.internal.test.InternalBaseTestCase;
import org.apache.tapestry5.internal.transform.InheritedAnnotation;
import org.apache.tapestry5.internal.transform.TestPackageAwareLoader;
import org.apache.tapestry5.internal.transform.pages.AbstractFoo;
import org.apache.tapestry5.internal.transform.pages.BarImpl;
import org.apache.tapestry5.internal.transform.pages.ChildClassInheritsAnnotation;
import org.apache.tapestry5.internal.transform.pages.ClaimedFields;
import org.apache.tapestry5.internal.transform.pages.EventHandlerTarget;
import org.apache.tapestry5.internal.transform.pages.FieldAccessBean;
import org.apache.tapestry5.internal.transform.pages.MethodAccessSubject;
import org.apache.tapestry5.internal.transform.pages.MethodIdentifier;
import org.apache.tapestry5.internal.transform.pages.ParentClass;
import org.apache.tapestry5.internal.transform.pages.ReadOnlyBean;
import org.apache.tapestry5.internal.transform.pages.TargetObject;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryClassPool;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
import org.apache.tapestry5.ioc.internal.services.CtClassSourceImpl;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.PropertyAccess;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.runtime.ComponentResourcesAware;
import org.apache.tapestry5.services.ClassTransformation;
import org.apache.tapestry5.services.ComponentClassTransformWorker;
import org.apache.tapestry5.services.ComponentMethodAdvice;
import org.apache.tapestry5.services.ComponentMethodInvocation;
import org.apache.tapestry5.services.MethodAccess;
import org.apache.tapestry5.services.MethodFilter;
import org.apache.tapestry5.services.MethodInvocationResult;
import org.apache.tapestry5.services.TransformMethod;
import org.apache.tapestry5.services.TransformMethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
public class InternalClassTransformationImplTest extends InternalBaseTestCase
{
    private static final String STRING_CLASS_NAME = "java.lang.String";

    private PropertyAccess access;

    private final ClassLoader contextClassLoader = currentThread().getContextClassLoader();

    private ClassFactory classFactory;

    private Loader loader;

    private ClassFactoryClassPool classFactoryClassPool;

    private CtClassSourceImpl classSource;

    @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()
    {
        ClassLoader threadDeadlockBuffer = new URLClassLoader(new URL[0], contextClassLoader);

        classFactoryClassPool = new ClassFactoryClassPool(threadDeadlockBuffer);

        loader = new TestPackageAwareLoader(threadDeadlockBuffer, classFactoryClassPool);

        // Inside Maven Surefire, the system classpath is not sufficient to find all
        // the necessary files.
        classFactoryClassPool.appendClassPath(new LoaderClassPath(loader));

        Logger logger = LoggerFactory.getLogger(InternalClassTransformationImplTest.class);

        classFactory = new ClassFactoryImpl(loader, classFactoryClassPool, logger);

        classSource = new CtClassSourceImpl(classFactoryClassPool, loader);
    }

    private Object transform(Class componentClass, ComponentClassTransformWorker worker) throws Exception
    {
        InternalComponentResources resources = mockInternalComponentResources();

        CtClass targetObjectCtClass = findCtClass(componentClass);

        Logger logger = mockLogger();
        MutableComponentModel model = mockMutableComponentModel(logger);

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(classFactory, targetObjectCtClass,
                new ComponentClassCacheImpl(classFactory, null), model, classSource, false);

        worker.transform(ct, model);

        ct.finish();

        Instantiator instantiator = ct.createInstantiator();

        Component instance = instantiator.newInstance(resources);

        verify();

        expect(resources.getComponent()).andReturn(instance).anyTimes();

        replay();

        // Return the instance for further testing

        return instance;
    }

    private CtClass findCtClass(Class targetClass) throws NotFoundException
    {
        return classFactoryClassPool.get(targetClass.getName());
    }

    private Class toClass(CtClass ctClass) throws Exception
    {
        return classFactoryClassPool.toClass(ctClass, loader, null);
    }

    @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);

        MutableComponentModel model = stubMutableComponentModel(logger);

        return new InternalClassTransformationImpl(classFactory, ctClass, null, model, null, false);
    }

    private MutableComponentModel stubMutableComponentModel(Logger logger)
    {
        return new MutableComponentModelImpl("unknown-class", logger, null, 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.tapestry5.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 annotation, but not that annotation
        assertNull(ct.getFieldAnnotation("_annotatedField", Override.class));

        // Field with no annotation
        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 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.tapestry5.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 annotation 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();
    }

    // TAPESTRY-2481
    @Test
    public void ensure_only_inherited_annotations_from_parent_class_are_visible() throws Exception
    {
        Logger logger = mockLogger();

        replay();

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

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

        assertNull(meta);

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

    public static final TransformMethodSignature RUN = new TransformMethodSignature("run");

    @Test
    public void access_to_protected_void_no_args_method() throws Exception
    {
        Object instance = transform(MethodAccessSubject.class, new ComponentClassTransformWorker()
        {
            public void transform(ClassTransformation transformation, MutableComponentModel model)
            {
                transformation.addImplementedInterface(Runnable.class);

                TransformMethodSignature targetMethodSignature = new TransformMethodSignature(Modifier.PROTECTED,
                        "void", "protectedVoidNoArgs", null, null);
                TransformMethod pvna = transformation.getOrCreateMethod(targetMethodSignature);

                final MethodAccess pvnaAccess = pvna.getAccess();

                transformation.getOrCreateMethod(RUN).addAdvice(new ComponentMethodAdvice()
                {
                    public void advise(ComponentMethodInvocation invocation)
                    {
                        invocation.proceed();

                        MethodInvocationResult invocationResult = pvnaAccess.invoke(invocation.getInstance());

                        assertFalse(invocationResult.isFail(), "fail should be false, no checked exception thrown");
                    }
                });
            }
        });

        Runnable r = (Runnable) instance;

        r.run();

        assertEquals(access.get(r, "marker"), "protectedVoidNoArgs");
    }

    @Test
    public void access_to_public_void_throws_exception() throws Exception
    {
        Object instance = transform(MethodAccessSubject.class, new ComponentClassTransformWorker()
        {
            public void transform(ClassTransformation transformation, MutableComponentModel model)
            {
                transformation.addImplementedInterface(Runnable.class);

                TransformMethodSignature targetMethodSignature = new TransformMethodSignature(Modifier.PUBLIC, "void",
                        "publicVoidThrowsException", null, new String[]
                        { SQLException.class.getName() });
                TransformMethod targetMethod = transformation.getOrCreateMethod(targetMethodSignature);

                final MethodAccess targetAccess = targetMethod.getAccess();

                transformation.getOrCreateMethod(RUN).addAdvice(new ComponentMethodAdvice()
                {
                    public void advise(ComponentMethodInvocation invocation)
                    {
                        invocation.proceed();

                        MethodInvocationResult invocationResult = targetAccess.invoke(invocation.getInstance());

                        assertTrue(invocationResult.isFail(), "fail should be true; checked exception thrown");

                        SQLException ex = invocationResult.getThrown(SQLException.class);

                        assertNotNull(ex);
                        assertEquals(ex.getMessage(), "From publicVoidThrowsException()");
                    }
                });
            }
        });

        Runnable r = (Runnable) instance;

        r.run();

        assertEquals(access.get(r, "marker"), "publicVoidThrowsException");
    }

    public interface ProcessInteger
    {
        int operate(int input);
    }

    @Test
    public void access_to_public_method_with_argument_and_return_value() throws Exception
    {
        Object instance = transform(MethodAccessSubject.class, new ComponentClassTransformWorker()
        {
            public void transform(ClassTransformation transformation, MutableComponentModel model)
            {
                transformation.addImplementedInterface(ProcessInteger.class);

                TransformMethod incrementer = transformation.getOrCreateMethod(new TransformMethodSignature(
                        Modifier.PUBLIC, "int", "incrementer", new String[]
                        { "int" }, null));

                final MethodAccess incrementerAccess = incrementer.getAccess();

                TransformMethodSignature operateSig = new TransformMethodSignature(Modifier.PUBLIC, "int", "operate",
                        new String[]
                        { "int" }, null);

                TransformMethod operate = transformation.getOrCreateMethod(operateSig);

                operate.addAdvice(new ComponentMethodAdvice()
                {
                    public void advise(ComponentMethodInvocation invocation)
                    {
                        // This advice *replaces* the original do-nothing method, because
                        // it never calls invocation.proceed().

                        // This kind of advice always needs some special knowledge of
                        // the parameters to the original method, so that they can be mapped
                        // to some other method (including a MethodAccess).

                        Integer parameter = (Integer) invocation.getParameter(0);

                        MethodInvocationResult result = incrementerAccess.invoke(invocation.getInstance(), parameter);

                        invocation.overrideResult(result.getReturnValue());
                    }
                });
            }
        });

        ProcessInteger pi = (ProcessInteger) instance;

        assertEquals(pi.operate(99), 100);

        assertEquals(access.get(instance, "marker"), "incrementer(99)");
    }

    public interface ProcessStringAndInteger
    {
        String process(String input, int value);
    }

    @Test
    public void access_to_private_method() throws Exception
    {
        Object instance = transform(MethodAccessSubject.class, new ComponentClassTransformWorker()
        {
            public void transform(ClassTransformation transformation, MutableComponentModel model)
            {
                transformation.addImplementedInterface(ProcessStringAndInteger.class);

                TransformMethod targetMethod = transformation.getOrCreateMethod(new TransformMethodSignature(
                        Modifier.PRIVATE, "java.lang.String", "privateMethod", new String[]
                        { "java.lang.String", "int" }, null));

                final MethodAccess targetMethodAccess = targetMethod.getAccess();

                TransformMethodSignature processSig = new TransformMethodSignature(Modifier.PUBLIC, "java.lang.String",
                        "process", new String[]
                        { "java.lang.String", "int" }, null);

                TransformMethod process = transformation.getOrCreateMethod(processSig);

                process.addAdvice(new ComponentMethodAdvice()
                {
                    public void advise(ComponentMethodInvocation invocation)
                    {
                        // Don't even bother with proceed() this time, which is OK (but
                        // somewhat rare).

                        MethodInvocationResult result = targetMethodAccess.invoke(invocation.getInstance(), invocation
                                .getParameter(0), invocation.getParameter(1));

                        invocation.overrideResult(result.getReturnValue());
                    }
                });
            }
        });

        ProcessStringAndInteger p = (ProcessStringAndInteger) instance;

        assertEquals(p.process("Tapestry!", 2), "Tapestry!Tapestry!");

        assertEquals(access.get(instance, "marker"), "privateMethod");
    }

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

        CtClass targetObjectCtClass = findCtClass(TargetObject.class);

        Logger logger = mockLogger();
        MutableComponentModel model = mockMutableComponentModel(logger);

        replay();

        InternalClassTransformation ct = new InternalClassTransformationImpl(classFactory, targetObjectCtClass, null,
                model, null, false);

        // Default behavior is to add an injected field for the InternalComponentResources object,
        // so we'll just check that.

        ct.finish();

        Instantiator instantiator = ct.createInstantiator();

        ComponentResourcesAware instance = instantiator.newInstance(resources);

        assertSame(instance.getComponentResources(), resources);

        verify();
    }

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

        Logger logger = mockLogger();
        MutableComponentModel model = mockMutableComponentModel(logger);

        replay();

        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(classFactory, targetObjectCtClass, null,
                model, null, false);

        ct.makeReadOnly("_value");

        ct.finish();

        Object target = instantiate(ReadOnlyBean.class, ct, resources);

        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.tapestry5.internal.transform.pages.ReadOnlyBean._value is read-only.");
        }

        verify();
    }

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

        Logger logger = mockLogger();
        MutableComponentModel model = mockMutableComponentModel(logger);

        replay();

        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(classFactory, targetObjectCtClass, null,
                model, null, false);

        ct.injectField("_value", "Tapestry");

        ct.finish();

        Object target = instantiate(ReadOnlyBean.class, ct, resources);

        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.tapestry5.internal.transform.pages.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();
        MutableComponentModel model = mockMutableComponentModel(logger);

        replay();

        CtClass targetObjectCtClass = findCtClass(FieldAccessBean.class);

        InternalClassTransformation ct = new InternalClassTransformationImpl(classFactory, targetObjectCtClass, null,
                model, null, false);

        replaceAccessToField(ct, "foo");
        replaceAccessToField(ct, "bar");

        // Stuff ...

        ct.finish();

        Object target = instantiate(FieldAccessBean.class, ct, 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.

        checkReplacedFieldAccess(target, "foo");
        checkReplacedFieldAccess(target, "bar");

        verify();
    }

    private void checkReplacedFieldAccess(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.addNewMethod(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.addNewMethod(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.tapestry5.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.tapestry5.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_are_an_exception() throws Exception
    {
        Logger logger = mockLogger();

        replay();

        try
        {

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

            unreachable();
        }
        catch (RuntimeException ex)
        {
            assertMessageContains(ex, "Class " + VisibilityBean.class.getName() + " contains field(s)",
                    "_$myPackagePrivate", "_$myProtected", "_$myPublic");
        }

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

    private Component instantiate(Class<?> expectedClass, InternalClassTransformation ct,
            InternalComponentResources resources) throws Exception
    {
        Instantiator ins = ct.createInstantiator();

        return ins.newInstance(resources);
    }

    @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.tapestry5.internal.transform.pages.MethodIdentifier.makeWaves(java.lang.String, int[]) (at MethodIdentifier.java:24)");

        verify();
    }

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

        replay();

        MethodFilter filter = new MethodFilter()
        {
            public boolean accept(TransformMethodSignature signature)
            {
                return true;
            }
        };

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

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

        assertFalse(methods.isEmpty());

        for (TransformMethodSignature sig : methods)
        {
            assertFalse(ct.isMethodOverride(sig));
        }

        verify();
    }

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

        replay();

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

        TransformMethodSignature sig = new TransformMethodSignature("methodDoesNotExist");

        try
        {
            ct.isMethodOverride(sig);
            unreachable();
        }
        catch (IllegalArgumentException ex)
        {
            assertEquals(
                    ex.getMessage(),
                    "Method public void methodDoesNotExist() is not implemented by transformed class org.apache.tapestry5.internal.services.SimpleBean.");
        }

        verify();

    }

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

        replay();

        InternalClassTransformation parentTransform = createClassTransformation(SimpleBean.class, logger);

        parentTransform.finish();

        CtClass childClass = findCtClass(SimpleBeanSubclass.class);

        ClassTransformation childTransform = parentTransform.createChildTransformation(childClass,
                stubMutableComponentModel(logger));

        assertFalse(childTransform.isMethodOverride(new TransformMethodSignature("notOverridden")));

        assertTrue(childTransform.isMethodOverride(new TransformMethodSignature(Modifier.PUBLIC, "void", "setAge",
                new String[]
                { "int" }, null)));
    }

}
TOP

Related Classes of org.apache.tapestry5.internal.services.InternalClassTransformationImplTest$ProcessStringAndInteger

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.