0001: // Copyright 2006, 2007 The Apache Software Foundation
0002: //
0003: // Licensed under the Apache License, Version 2.0 (the "License");
0004: // you may not use this file except in compliance with the License.
0005: // You may obtain a copy of the License at
0006: //
0007: // http://www.apache.org/licenses/LICENSE-2.0
0008: //
0009: // Unless required by applicable law or agreed to in writing, software
0010: // distributed under the License is distributed on an "AS IS" BASIS,
0011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012: // See the License for the specific language governing permissions and
0013: // limitations under the License.
0014:
0015: package org.apache.tapestry.internal.services;
0016:
0017: import static java.lang.Thread.currentThread;
0018: import static java.util.Arrays.asList;
0019:
0020: import java.lang.annotation.Documented;
0021: import java.lang.annotation.Target;
0022: import java.lang.reflect.Field;
0023: import java.lang.reflect.Modifier;
0024: import java.util.Arrays;
0025: import java.util.List;
0026: import java.util.Map;
0027:
0028: import javassist.ClassPool;
0029: import javassist.CtClass;
0030: import javassist.CtMethod;
0031: import javassist.Loader;
0032: import javassist.LoaderClassPath;
0033: import javassist.NotFoundException;
0034:
0035: import org.apache.commons.logging.Log;
0036: import org.apache.tapestry.annotations.Meta;
0037: import org.apache.tapestry.annotations.OnEvent;
0038: import org.apache.tapestry.annotations.Retain;
0039: import org.apache.tapestry.annotations.SetupRender;
0040: import org.apache.tapestry.internal.InternalComponentResources;
0041: import org.apache.tapestry.internal.test.InternalBaseTestCase;
0042: import org.apache.tapestry.internal.transform.InheritedAnnotation;
0043: import org.apache.tapestry.internal.transform.pages.AbstractFoo;
0044: import org.apache.tapestry.internal.transform.pages.BarImpl;
0045: import org.apache.tapestry.internal.transform.pages.BasicComponent;
0046: import org.apache.tapestry.internal.transform.pages.ChildClassInheritsAnnotation;
0047: import org.apache.tapestry.internal.transform.pages.ClaimedFields;
0048: import org.apache.tapestry.internal.transform.pages.EventHandlerTarget;
0049: import org.apache.tapestry.internal.transform.pages.FindFieldClass;
0050: import org.apache.tapestry.internal.transform.pages.MethodIdentifier;
0051: import org.apache.tapestry.internal.transform.pages.ParentClass;
0052: import org.apache.tapestry.internal.transform.pages.TargetObject;
0053: import org.apache.tapestry.internal.transform.pages.TargetObjectSubclass;
0054: import org.apache.tapestry.ioc.internal.services.PropertyAccessImpl;
0055: import org.apache.tapestry.ioc.services.PropertyAccess;
0056: import org.apache.tapestry.runtime.Component;
0057: import org.apache.tapestry.runtime.ComponentResourcesAware;
0058: import org.apache.tapestry.services.ClassTransformation;
0059: import org.apache.tapestry.services.MethodFilter;
0060: import org.apache.tapestry.services.MethodSignature;
0061: import org.testng.annotations.AfterClass;
0062: import org.testng.annotations.BeforeClass;
0063: import org.testng.annotations.BeforeMethod;
0064: import org.testng.annotations.Test;
0065:
0066: /**
0067: * The tests share a number of resources, and so are run sequentially.
0068: */
0069: @Test(sequential=true)
0070: public class InternalClassTransformationImplTest extends
0071: InternalBaseTestCase {
0072: private static final String STRING_CLASS_NAME = "java.lang.String";
0073:
0074: private ClassPool _classPool;
0075:
0076: private final ClassLoader _contextClassLoader = currentThread()
0077: .getContextClassLoader();
0078:
0079: private Loader _loader;
0080:
0081: private PropertyAccess _access;
0082:
0083: @BeforeClass
0084: public void setup_access() {
0085: _access = getService("PropertyAccess", PropertyAccess.class);
0086: }
0087:
0088: @AfterClass
0089: public void cleanup_access() {
0090: _access = null;
0091: }
0092:
0093: /**
0094: * We need a new ClassPool for each individual test, since many of the tests will end up
0095: * modifying one or more CtClass instances.
0096: */
0097: @BeforeMethod
0098: public void setup_classpool() {
0099: _classPool = new ClassPool();
0100:
0101: _loader = new Loader(_contextClassLoader, _classPool);
0102:
0103: // This ensures that only the classes we explicitly access and modify
0104: // are loaded by the new loader; everthing else comes out of the common
0105: // context class loader, which prevents a lot of nasty class cast exceptions.
0106:
0107: _loader.delegateLoadingOf("org.apache.tapestry.");
0108:
0109: // Inside Maven Surefire, the system classpath is not sufficient to find all
0110: // the necessary files.
0111: _classPool.appendClassPath(new LoaderClassPath(_loader));
0112: }
0113:
0114: private CtClass findCtClass(Class targetClass)
0115: throws NotFoundException {
0116: return _classPool.get(targetClass.getName());
0117: }
0118:
0119: @Test
0120: public void new_member_name() throws Exception {
0121: Log log = mockLog();
0122:
0123: replay();
0124:
0125: ClassTransformation ct = createClassTransformation(
0126: ParentClass.class, log);
0127:
0128: assertEquals(ct.newMemberName("fred"), "_$fred");
0129: assertEquals(ct.newMemberName("fred"), "_$fred_0");
0130:
0131: // Here we're exposing a bit of the internal algorithm, which strips
0132: // off '$' and '_' before tacking "_$" in front.
0133:
0134: assertEquals(ct.newMemberName("_fred"), "_$fred_1");
0135: assertEquals(ct.newMemberName("_$fred"), "_$fred_2");
0136: assertEquals(ct.newMemberName("__$___$____$_fred"), "_$fred_3");
0137:
0138: // Here we're trying to force conflicts with existing declared
0139: // fields and methods of the class.
0140:
0141: assertEquals(ct.newMemberName("_parentField"), "_$parentField");
0142: assertEquals(ct.newMemberName("conflictField"),
0143: "_$conflictField_0");
0144: assertEquals(ct.newMemberName("conflictMethod"),
0145: "_$conflictMethod_0");
0146:
0147: verify();
0148: }
0149:
0150: @Test
0151: public void new_member_name_with_prefix() throws Exception {
0152: Log log = mockLog();
0153:
0154: replay();
0155:
0156: ClassTransformation ct = createClassTransformation(
0157: ParentClass.class, log);
0158:
0159: assertEquals(ct.newMemberName("prefix", "fred"),
0160: "_$prefix_fred");
0161: assertEquals(ct.newMemberName("prefix", "fred"),
0162: "_$prefix_fred_0");
0163:
0164: // Here we're exposing a bit of the internal algorithm, which strips
0165: // off '$' and '_' before tacking "_$" in front.
0166:
0167: assertEquals(ct.newMemberName("prefix", "_fred"),
0168: "_$prefix_fred_1");
0169: assertEquals(ct.newMemberName("prefix", "_$fred"),
0170: "_$prefix_fred_2");
0171: assertEquals(ct.newMemberName("prefix", "__$___$____$_fred"),
0172: "_$prefix_fred_3");
0173:
0174: verify();
0175: }
0176:
0177: private InternalClassTransformation createClassTransformation(
0178: Class targetClass, Log log) throws NotFoundException {
0179: CtClass ctClass = findCtClass(targetClass);
0180:
0181: return new InternalClassTransformationImpl(ctClass,
0182: _contextClassLoader, log, null);
0183: }
0184:
0185: @Test
0186: public void find_annotation_on_unknown_field() throws Exception {
0187: Log log = mockLog();
0188:
0189: replay();
0190:
0191: ClassTransformation ct = createClassTransformation(
0192: ParentClass.class, log);
0193:
0194: try {
0195: ct.getFieldAnnotation("unknownField", Retain.class);
0196: unreachable();
0197: } catch (RuntimeException ex) {
0198: assertEquals(
0199: ex.getMessage(),
0200: "Class org.apache.tapestry.internal.transform.pages.ParentClass does not contain a field named 'unknownField'.");
0201: }
0202:
0203: verify();
0204: }
0205:
0206: @Test
0207: public void find_field_annotation() throws Exception {
0208: Log log = mockLog();
0209:
0210: replay();
0211:
0212: ClassTransformation ct = createClassTransformation(
0213: ParentClass.class, log);
0214:
0215: Retain retain = ct.getFieldAnnotation("_annotatedField",
0216: Retain.class);
0217:
0218: assertNotNull(retain);
0219:
0220: verify();
0221: }
0222:
0223: @Test
0224: public void field_does_not_contain_requested_annotation()
0225: throws Exception {
0226: Log log = mockLog();
0227:
0228: replay();
0229:
0230: ClassTransformation ct = createClassTransformation(
0231: ParentClass.class, log);
0232:
0233: // Field with annotations, but not that annotation
0234: assertNull(ct.getFieldAnnotation("_annotatedField",
0235: Override.class));
0236:
0237: // Field with no annotations
0238: assertNull(ct
0239: .getFieldAnnotation("_parentField", Override.class));
0240:
0241: verify();
0242: }
0243:
0244: @Test
0245: public void find_fields_with_annotation() throws Exception {
0246: Log log = mockLog();
0247:
0248: replay();
0249:
0250: ClassTransformation ct = createClassTransformation(
0251: ParentClass.class, log);
0252:
0253: List<String> fields = ct.findFieldsWithAnnotation(Retain.class);
0254:
0255: assertEquals(fields.size(), 1);
0256: assertEquals(fields.get(0), "_annotatedField");
0257:
0258: verify();
0259: }
0260:
0261: @Test
0262: public void find_fields_of_type() throws Exception {
0263: Log log = mockLog();
0264:
0265: replay();
0266:
0267: ClassTransformation ct = createClassTransformation(
0268: FindFieldClass.class, log);
0269:
0270: checkFindFields(ct, "boolean", "_booleanValue");
0271: checkFindFields(ct, "int[]", "_intArrayValue");
0272: checkFindFields(ct, "java.lang.String", "_stringValue");
0273: checkFindFields(ct, "java.util.Date[]", "_dateArrayValue");
0274:
0275: verify();
0276: }
0277:
0278: @Test
0279: public void get_field_modifiers() throws Exception {
0280: Log log = mockLog();
0281:
0282: replay();
0283:
0284: ClassTransformation ct = createClassTransformation(
0285: CheckFieldType.class, log);
0286:
0287: assertEquals(ct.getFieldModifiers("_privateField"),
0288: Modifier.PRIVATE);
0289: assertEquals(ct.getFieldModifiers("_map"), Modifier.PRIVATE
0290: + Modifier.FINAL);
0291: }
0292:
0293: @Test
0294: public void get_field_exists() throws Exception {
0295: Log log = mockLog();
0296:
0297: replay();
0298:
0299: ClassTransformation ct = createClassTransformation(
0300: CheckFieldType.class, log);
0301:
0302: assertTrue(ct.isField("_privateField"));
0303: assertFalse(ct.isField("_doesNotExist"));
0304:
0305: verify();
0306: }
0307:
0308: @Test
0309: public void find_fields_of_type_excludes_claimed_fields()
0310: throws Exception {
0311: Log log = mockLog();
0312:
0313: replay();
0314:
0315: ClassTransformation ct = createClassTransformation(
0316: FindFieldClass.class, log);
0317:
0318: ct.claimField("_booleanValue", this );
0319:
0320: checkFindFields(ct, "boolean");
0321:
0322: verify();
0323: }
0324:
0325: private void checkFindFields(ClassTransformation ct,
0326: String fieldType, String... expectedNames) {
0327: List<String> actual = ct.findFieldsOfType(fieldType);
0328:
0329: assertEquals(actual, Arrays.asList(expectedNames));
0330: }
0331:
0332: @Test
0333: public void find_fields_with_annotation_excludes_claimed_files()
0334: throws Exception {
0335: Log log = mockLog();
0336:
0337: replay();
0338:
0339: ClassTransformation ct = createClassTransformation(
0340: ParentClass.class, log);
0341:
0342: ct.claimField("_annotatedField", this );
0343:
0344: List<String> fields = ct.findFieldsWithAnnotation(Retain.class);
0345:
0346: assertTrue(fields.isEmpty());
0347:
0348: verify();
0349: }
0350:
0351: @Test
0352: public void no_fields_contain_requested_annotation()
0353: throws Exception {
0354: Log log = mockLog();
0355:
0356: replay();
0357:
0358: ClassTransformation ct = createClassTransformation(
0359: ParentClass.class, log);
0360:
0361: List<String> fields = ct
0362: .findFieldsWithAnnotation(Documented.class);
0363:
0364: assertTrue(fields.isEmpty());
0365:
0366: verify();
0367: }
0368:
0369: @Test
0370: public void claim_fields() throws Exception {
0371: Log log = mockLog();
0372:
0373: replay();
0374:
0375: ClassTransformation ct = createClassTransformation(
0376: ClaimedFields.class, log);
0377:
0378: List<String> unclaimed = ct.findUnclaimedFields();
0379:
0380: assertEquals(unclaimed,
0381: asList("_field1", "_field4", "_zzfield"));
0382:
0383: ct.claimField("_field4", "Fred");
0384:
0385: unclaimed = ct.findUnclaimedFields();
0386:
0387: assertEquals(unclaimed, asList("_field1", "_zzfield"));
0388:
0389: try {
0390: ct.claimField("_field4", "Barney");
0391: unreachable();
0392: } catch (RuntimeException ex) {
0393: assertEquals(
0394: ex.getMessage(),
0395: "Field _field4 of class org.apache.tapestry.internal.transform.pages.ClaimedFields is already claimed by Fred and can not be claimed by Barney.");
0396: }
0397:
0398: verify();
0399: }
0400:
0401: @Test
0402: public void added_fields_are_not_listed_as_unclaimed_fields()
0403: throws Exception {
0404: Log log = mockLog();
0405:
0406: replay();
0407:
0408: ClassTransformation ct = createClassTransformation(
0409: ClaimedFields.class, log);
0410:
0411: ct.addField(Modifier.PRIVATE, "int", "newField");
0412:
0413: List<String> unclaimed = ct.findUnclaimedFields();
0414:
0415: assertEquals(unclaimed,
0416: asList("_field1", "_field4", "_zzfield"));
0417:
0418: verify();
0419: }
0420:
0421: @Test
0422: public void find_class_annotations() throws Exception {
0423: Log log = mockLog();
0424:
0425: replay();
0426:
0427: ClassTransformation ct = createClassTransformation(
0428: ParentClass.class, log);
0429:
0430: Meta meta = ct.getAnnotation(Meta.class);
0431:
0432: assertNotNull(meta);
0433:
0434: // Try again (the annotations will be cached). Use an annotation
0435: // that will not be present.
0436:
0437: Target t = ct.getAnnotation(Target.class);
0438:
0439: assertNull(t);
0440:
0441: verify();
0442: }
0443:
0444: /**
0445: * More a test of how Javassist works. Javassist does not honor the Inherited annotation for
0446: * classes (this kind of makes sense, since it won't necessarily have the super-class in
0447: * memory).
0448: */
0449: @Test
0450: public void ensure_subclasses_inherit_parent_class_annotations()
0451: throws Exception {
0452: // The Java runtime does honor @Inherited
0453: assertNotNull(ChildClassInheritsAnnotation.class
0454: .getAnnotation(InheritedAnnotation.class));
0455:
0456: Log log = mockLog();
0457:
0458: replay();
0459:
0460: ClassTransformation ct = createClassTransformation(
0461: ChildClassInheritsAnnotation.class, log);
0462:
0463: InheritedAnnotation ia = ct
0464: .getAnnotation(InheritedAnnotation.class);
0465:
0466: // Javassist does not, but ClassTransformation patches around that.
0467:
0468: assertNotNull(ia);
0469:
0470: verify();
0471: }
0472:
0473: /**
0474: * These tests are really to assert my understanding of Javassist's API. I guess we should keep
0475: * them around to make sure that future versions of Javassist work the same as our expectations.
0476: */
0477: @Test
0478: public void ensure_javassist_still_does_not_show_inherited_interfaces()
0479: throws Exception {
0480: CtClass ctClass = findCtClass(BarImpl.class);
0481:
0482: CtClass[] interfaces = ctClass.getInterfaces();
0483:
0484: // Just the interfaces implemented by this particular class, not
0485: // inherited interfaces.
0486:
0487: assertEquals(interfaces.length, 1);
0488:
0489: assertEquals(interfaces[0].getName(), BarInterface.class
0490: .getName());
0491:
0492: CtClass parentClass = ctClass.getSuperclass();
0493:
0494: interfaces = parentClass.getInterfaces();
0495:
0496: assertEquals(interfaces.length, 1);
0497:
0498: assertEquals(interfaces[0].getName(), FooInterface.class
0499: .getName());
0500: }
0501:
0502: @Test
0503: public void ensure_javassist_does_not_show_interface_methods_on_abstract_class()
0504: throws Exception {
0505: CtClass ctClass = findCtClass(AbstractFoo.class);
0506:
0507: CtClass[] interfaces = ctClass.getInterfaces();
0508:
0509: assertEquals(interfaces.length, 1);
0510:
0511: assertEquals(interfaces[0].getName(), FooInterface.class
0512: .getName());
0513:
0514: // In some cases, Java reflection on an abstract class implementing an interface
0515: // will show the interface methods as abstract methods on the class. This seems
0516: // to vary from JVM to JVM. I believe Javassist is more consistent here.
0517:
0518: CtMethod[] methods = ctClass.getDeclaredMethods();
0519:
0520: assertEquals(methods.length, 0);
0521: }
0522:
0523: @Test
0524: public void ensure_javassist_does_not_show_extended_interface_methods_on_interface()
0525: throws Exception {
0526: CtClass ctClass = findCtClass(FooBarInterface.class);
0527:
0528: // Just want to check that an interface that extends other interfaces
0529: // doesn't show those other interface's methods.
0530:
0531: CtMethod[] methods = ctClass.getDeclaredMethods();
0532:
0533: assertEquals(methods.length, 0);
0534: }
0535:
0536: @Test
0537: public void add_injected_field() throws Exception {
0538: InternalComponentResources resources = mockInternalComponentResources();
0539:
0540: CtClass targetObjectCtClass = findCtClass(TargetObject.class);
0541:
0542: Log log = mockLog();
0543:
0544: replay();
0545:
0546: InternalClassTransformation ct = new InternalClassTransformationImpl(
0547: targetObjectCtClass, _contextClassLoader, log, null);
0548:
0549: // Default behavior is to add an injected field for the InternalComponentResources object,
0550: // so we'll just check that.
0551:
0552: ct.finish();
0553:
0554: Class transformed = _classPool.toClass(targetObjectCtClass,
0555: _loader);
0556:
0557: Instantiator instantiator = ct.createInstantiator(transformed);
0558:
0559: ComponentResourcesAware instance = instantiator
0560: .newInstance(resources);
0561:
0562: assertSame(instance.getComponentResources(), resources);
0563:
0564: verify();
0565: }
0566:
0567: @Test
0568: public void add_injected_field_from_parent_transformation()
0569: throws Exception {
0570: final String value = "from the parent";
0571:
0572: InternalComponentResources resources = mockInternalComponentResources();
0573:
0574: CtClass targetObjectCtClass = findCtClass(TargetObject.class);
0575:
0576: Log log = mockLog();
0577:
0578: replay();
0579:
0580: InternalClassTransformation ct = new InternalClassTransformationImpl(
0581: targetObjectCtClass, _loader, log, null);
0582:
0583: String parentFieldName = ct.addInjectedField(String.class,
0584: "_value", value);
0585:
0586: // Default behavior is to add an injected field for the InternalComponentResources object,
0587: // so we'll just check that.
0588:
0589: ct.finish();
0590:
0591: // Instantiate the transformed base class, so that we can create a transformed
0592: // subclass.
0593:
0594: _classPool.toClass(targetObjectCtClass, _loader);
0595:
0596: // Now lets work on the subclass
0597:
0598: CtClass subclassCtClass = findCtClass(TargetObjectSubclass.class);
0599:
0600: ct = new InternalClassTransformationImpl(subclassCtClass, ct,
0601: _loader, log, null);
0602:
0603: String subclassFieldName = ct.addInjectedField(String.class,
0604: "_childValue", value);
0605:
0606: // This is what proves it is cached.
0607:
0608: assertEquals(subclassFieldName, parentFieldName);
0609:
0610: // This proves the the field is protected and can be used in subclasses.
0611:
0612: ct.addMethod(new MethodSignature(Modifier.PUBLIC,
0613: "java.lang.String", "getValue", null, null), "return "
0614: + subclassFieldName + ";");
0615:
0616: ct.finish();
0617:
0618: Class transformed = _classPool
0619: .toClass(subclassCtClass, _loader);
0620:
0621: Instantiator instantiator = ct.createInstantiator(transformed);
0622:
0623: Object instance = instantiator.newInstance(resources);
0624:
0625: Object actual = _access.get(instance, "value");
0626:
0627: assertSame(actual, value);
0628:
0629: verify();
0630: }
0631:
0632: @Test
0633: public void wrong_instance_type_passed_to_create_instantiator()
0634: throws Exception {
0635: CtClass ctClass = findCtClass(BasicComponent.class);
0636:
0637: Log log = mockLog();
0638:
0639: replay();
0640:
0641: InternalClassTransformation ct = new InternalClassTransformationImpl(
0642: ctClass, _contextClassLoader, log, null);
0643:
0644: _classPool.toClass(ctClass, _loader);
0645:
0646: try {
0647: ct.createInstantiator(Boolean.class);
0648: unreachable();
0649: } catch (IllegalArgumentException ex) {
0650: assertEquals(ex.getMessage(), ServicesMessages
0651: .incorrectClassForInstantiator(BasicComponent.class
0652: .getName(), Boolean.class));
0653: }
0654:
0655: verify();
0656: }
0657:
0658: @Test
0659: public void add_interface_to_class() throws Exception {
0660: InternalComponentResources resources = mockInternalComponentResources();
0661:
0662: CtClass targetObjectCtClass = findCtClass(TargetObject.class);
0663:
0664: Log log = mockLog();
0665:
0666: replay();
0667:
0668: InternalClassTransformation ct = new InternalClassTransformationImpl(
0669: targetObjectCtClass, _contextClassLoader, log, null);
0670:
0671: ct.addImplementedInterface(FooInterface.class);
0672: ct.addImplementedInterface(GetterMethodsInterface.class);
0673:
0674: ct.finish();
0675:
0676: Class transformed = _classPool.toClass(targetObjectCtClass,
0677: _loader);
0678:
0679: Class[] interfaces = transformed.getInterfaces();
0680:
0681: assertEquals(interfaces, new Class[] { Component.class,
0682: FooInterface.class, GetterMethodsInterface.class });
0683:
0684: Object target = ct.createInstantiator(transformed).newInstance(
0685: resources);
0686:
0687: FooInterface asFoo = (FooInterface) target;
0688:
0689: asFoo.foo();
0690:
0691: GetterMethodsInterface getters = (GetterMethodsInterface) target;
0692:
0693: assertEquals(getters.getBoolean(), false);
0694: assertEquals(getters.getByte(), (byte) 0);
0695: assertEquals(getters.getShort(), (short) 0);
0696: assertEquals(getters.getInt(), 0);
0697: assertEquals(getters.getLong(), 0l);
0698: assertEquals(getters.getFloat(), 0.0f);
0699: assertEquals(getters.getDouble(), 0.0d);
0700: assertNull(getters.getString());
0701: assertNull(getters.getObjectArray());
0702: assertNull(getters.getIntArray());
0703:
0704: verify();
0705: }
0706:
0707: @Test
0708: public void make_field_read_only() throws Exception {
0709: InternalComponentResources resources = mockInternalComponentResources();
0710:
0711: Log log = mockLog();
0712:
0713: replay();
0714:
0715: CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
0716:
0717: InternalClassTransformation ct = new InternalClassTransformationImpl(
0718: targetObjectCtClass, _contextClassLoader, log, null);
0719:
0720: ct.makeReadOnly("_value");
0721:
0722: ct.finish();
0723:
0724: Class transformed = _classPool.toClass(targetObjectCtClass,
0725: _loader);
0726:
0727: Object target = ct.createInstantiator(transformed).newInstance(
0728: resources);
0729:
0730: PropertyAccess access = new PropertyAccessImpl();
0731:
0732: try {
0733: access.set(target, "value", "anything");
0734: unreachable();
0735: } catch (RuntimeException ex) {
0736: // The PropertyAccess layer adds a wrapper exception around the real one.
0737:
0738: assertEquals(ex.getCause().getMessage(),
0739: "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
0740: }
0741:
0742: verify();
0743: }
0744:
0745: @Test
0746: public void removed_fields_should_not_show_up_as_unclaimed()
0747: throws Exception {
0748: Log log = mockLog();
0749:
0750: replay();
0751:
0752: CtClass targetObjectCtClass = findCtClass(RemoveFieldBean.class);
0753:
0754: InternalClassTransformation ct = new InternalClassTransformationImpl(
0755: targetObjectCtClass, _contextClassLoader, log, null);
0756:
0757: ct.removeField("_barney");
0758:
0759: assertEquals(ct.findUnclaimedFields(), asList("_fred"));
0760:
0761: verify();
0762: }
0763:
0764: @Test
0765: public void add_to_constructor() throws Exception {
0766: InternalComponentResources resources = mockInternalComponentResources();
0767:
0768: Log log = mockLog();
0769:
0770: replay();
0771:
0772: CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
0773:
0774: InternalClassTransformation ct = new InternalClassTransformationImpl(
0775: targetObjectCtClass, _contextClassLoader, log, null);
0776:
0777: ct.extendConstructor("_value = \"from constructor\";");
0778:
0779: ct.finish();
0780:
0781: Class transformed = _classPool.toClass(targetObjectCtClass,
0782: _loader);
0783:
0784: Object target = ct.createInstantiator(transformed).newInstance(
0785: resources);
0786:
0787: PropertyAccess access = new PropertyAccessImpl();
0788:
0789: assertEquals(access.get(target, "value"), "from constructor");
0790:
0791: verify();
0792: }
0793:
0794: @Test
0795: public void inject_field() throws Exception {
0796: InternalComponentResources resources = mockInternalComponentResources();
0797:
0798: Log log = mockLog();
0799:
0800: replay();
0801:
0802: CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
0803:
0804: InternalClassTransformation ct = new InternalClassTransformationImpl(
0805: targetObjectCtClass, _contextClassLoader, log, null);
0806:
0807: ct.injectField("_value", "Tapestry");
0808:
0809: ct.finish();
0810:
0811: Class transformed = _classPool.toClass(targetObjectCtClass,
0812: _loader);
0813:
0814: Object target = ct.createInstantiator(transformed).newInstance(
0815: resources);
0816:
0817: PropertyAccess access = new PropertyAccessImpl();
0818:
0819: assertEquals(access.get(target, "value"), "Tapestry");
0820:
0821: try {
0822: access.set(target, "value", "anything");
0823: unreachable();
0824: } catch (RuntimeException ex) {
0825: // The PropertyAccess layer adds a wrapper exception around the real one.
0826:
0827: assertEquals(ex.getCause().getMessage(),
0828: "Field org.apache.tapestry.internal.services.ReadOnlyBean._value is read-only.");
0829: }
0830:
0831: verify();
0832: }
0833:
0834: /**
0835: * Tests the basic functionality of overriding read and write; also tests the case for multiple
0836: * field read/field write substitions.
0837: */
0838: @Test
0839: public void override_field_read_and_write() throws Exception {
0840: InternalComponentResources resources = mockInternalComponentResources();
0841:
0842: Log log = mockLog();
0843:
0844: replay();
0845:
0846: CtClass targetObjectCtClass = findCtClass(FieldAccessBean.class);
0847:
0848: InternalClassTransformation ct = new InternalClassTransformationImpl(
0849: targetObjectCtClass, _contextClassLoader, log, null);
0850:
0851: replaceAccessToField(ct, "foo");
0852: replaceAccessToField(ct, "bar");
0853:
0854: // Stuff ...
0855:
0856: ct.finish();
0857:
0858: Class transformed = _classPool.toClass(targetObjectCtClass,
0859: _loader);
0860:
0861: Object target = ct.createInstantiator(transformed).newInstance(
0862: resources);
0863:
0864: // target is no longer assignable to FieldAccessBean; its a new class from a new class
0865: // loader. So we use reflective access, which doesn't care about such things.
0866:
0867: PropertyAccess access = new PropertyAccessImpl();
0868:
0869: checkReplacedFieldAccess(access, target, "foo");
0870: checkReplacedFieldAccess(access, target, "bar");
0871:
0872: verify();
0873: }
0874:
0875: private void checkReplacedFieldAccess(PropertyAccess access,
0876: Object target, String propertyName) {
0877:
0878: try {
0879: access.get(target, propertyName);
0880: unreachable();
0881: } catch (RuntimeException ex) {
0882: // PropertyAccess adds a wrapper exception
0883: assertEquals(ex.getCause().getMessage(), "read "
0884: + propertyName);
0885: }
0886:
0887: try {
0888: access.set(target, propertyName, "new value");
0889: unreachable();
0890: } catch (RuntimeException ex) {
0891: // PropertyAccess adds a wrapper exception
0892: assertEquals(ex.getCause().getMessage(), "write "
0893: + propertyName);
0894: }
0895: }
0896:
0897: private void replaceAccessToField(InternalClassTransformation ct,
0898: String baseName) {
0899: String fieldName = "_" + baseName;
0900: String readMethodName = "_read_" + baseName;
0901:
0902: MethodSignature readMethodSignature = new MethodSignature(
0903: Modifier.PRIVATE, STRING_CLASS_NAME, readMethodName,
0904: null, null);
0905:
0906: ct.addMethod(readMethodSignature, String.format(
0907: "throw new RuntimeException(\"read %s\");", baseName));
0908:
0909: ct.replaceReadAccess(fieldName, readMethodName);
0910:
0911: String writeMethodName = "_write_" + baseName;
0912:
0913: MethodSignature writeMethodSignature = new MethodSignature(
0914: Modifier.PRIVATE, "void", writeMethodName,
0915: new String[] { STRING_CLASS_NAME }, null);
0916: ct.addMethod(writeMethodSignature, String.format(
0917: "throw new RuntimeException(\"write %s\");", baseName));
0918:
0919: ct.replaceWriteAccess(fieldName, writeMethodName);
0920: }
0921:
0922: @Test
0923: public void find_methods_with_annotation() throws Exception {
0924: Log log = mockLog();
0925:
0926: replay();
0927:
0928: ClassTransformation ct = createClassTransformation(
0929: AnnotatedPage.class, log);
0930:
0931: List<MethodSignature> l = ct
0932: .findMethodsWithAnnotation(SetupRender.class);
0933:
0934: // Check order
0935:
0936: assertEquals(l.size(), 2);
0937: assertEquals(l.get(0).toString(), "void beforeRender()");
0938: assertEquals(l.get(1).toString(),
0939: "boolean earlyRender(org.apache.tapestry.MarkupWriter)");
0940:
0941: // Check up on cacheing
0942:
0943: assertEquals(ct.findMethodsWithAnnotation(SetupRender.class), l);
0944:
0945: // Check up on no match.
0946:
0947: assertTrue(ct.findFieldsWithAnnotation(Deprecated.class)
0948: .isEmpty());
0949:
0950: verify();
0951: }
0952:
0953: @Test
0954: public void find_methods_using_filter() throws Exception {
0955: Log log = mockLog();
0956:
0957: replay();
0958:
0959: final ClassTransformation ct = createClassTransformation(
0960: AnnotatedPage.class, log);
0961:
0962: // Duplicates, somewhat less efficiently, the logic in find_methods_with_annotation().
0963:
0964: MethodFilter filter = new MethodFilter() {
0965: public boolean accept(MethodSignature signature) {
0966: return ct.getMethodAnnotation(signature,
0967: SetupRender.class) != null;
0968: }
0969: };
0970:
0971: List<MethodSignature> l = ct.findMethods(filter);
0972:
0973: // Check order
0974:
0975: assertEquals(l.size(), 2);
0976: assertEquals(l.get(0).toString(), "void beforeRender()");
0977: assertEquals(l.get(1).toString(),
0978: "boolean earlyRender(org.apache.tapestry.MarkupWriter)");
0979:
0980: // Check up on cacheing
0981:
0982: assertEquals(ct.findMethodsWithAnnotation(SetupRender.class), l);
0983:
0984: // Check up on no match.
0985:
0986: assertTrue(ct.findFieldsWithAnnotation(Deprecated.class)
0987: .isEmpty());
0988:
0989: verify();
0990: }
0991:
0992: @Test
0993: public void to_class_with_primitive_type() throws Exception {
0994: Log log = mockLog();
0995:
0996: replay();
0997:
0998: ClassTransformation ct = createClassTransformation(
0999: AnnotatedPage.class, log);
1000:
1001: assertSame(ct.toClass("float"), Float.class);
1002:
1003: verify();
1004: }
1005:
1006: @Test
1007: public void to_class_with_object_type() throws Exception {
1008: Log log = mockLog();
1009:
1010: replay();
1011:
1012: ClassTransformation ct = createClassTransformation(
1013: AnnotatedPage.class, log);
1014:
1015: assertSame(ct.toClass("java.util.Map"), Map.class);
1016:
1017: verify();
1018: }
1019:
1020: @Test
1021: public void non_private_fields_log_an_error() throws Exception {
1022: Log log = mockLog();
1023:
1024: log.error(ServicesMessages.nonPrivateFields(
1025: VisibilityBean.class.getName(), Arrays.asList(
1026: "_$myPackagePrivate", "_$myProtected",
1027: "_$myPublic")));
1028:
1029: replay();
1030:
1031: InternalClassTransformation ct = createClassTransformation(
1032: VisibilityBean.class, log);
1033:
1034: List<String> names = ct.findFieldsWithAnnotation(Retain.class);
1035:
1036: // Only _myLong shows up, because its the only private field
1037:
1038: assertEquals(names, Arrays.asList("_$myLong"));
1039:
1040: // However, all the fields are "reserved" via the IdAllocator ...
1041:
1042: assertEquals(ct.newMemberName("_$myLong"), "_$myLong_0");
1043: assertEquals(ct.newMemberName("_$myStatic"), "_$myStatic_0");
1044: assertEquals(ct.newMemberName("_$myProtected"),
1045: "_$myProtected_0");
1046:
1047: // The check for non-private fields has been moved from the ICTI constructor to the finish
1048: // method.
1049:
1050: ct.finish();
1051:
1052: verify();
1053: }
1054:
1055: @Test
1056: public void find_annotation_in_method() throws Exception {
1057: Log log = mockLog();
1058:
1059: replay();
1060:
1061: ClassTransformation ct = createClassTransformation(
1062: EventHandlerTarget.class, log);
1063:
1064: OnEvent annotation = ct.getMethodAnnotation(
1065: new MethodSignature("handler"), OnEvent.class);
1066:
1067: // Check that the attributes of the annotation match the expectation.
1068:
1069: assertEquals(annotation.value(), "fred");
1070: assertEquals(annotation.component(), "alpha");
1071:
1072: verify();
1073: }
1074:
1075: @Test
1076: public void find_annotation_in_unknown_method() throws Exception {
1077: Log log = mockLog();
1078:
1079: replay();
1080:
1081: ClassTransformation ct = createClassTransformation(
1082: ParentClass.class, log);
1083:
1084: try {
1085: ct.getMethodAnnotation(new MethodSignature("foo"),
1086: OnEvent.class);
1087: unreachable();
1088: } catch (IllegalArgumentException ex) {
1089: assertEquals(
1090: ex.getMessage(),
1091: "Class org.apache.tapestry.internal.transform.pages.ParentClass does not declare method 'public void foo()'.");
1092: }
1093:
1094: verify();
1095: }
1096:
1097: @Test
1098: public void remove_field() throws Exception {
1099: Log log = mockLog();
1100:
1101: replay();
1102:
1103: CtClass targetObjectCtClass = findCtClass(FieldRemoval.class);
1104:
1105: InternalClassTransformation ct = new InternalClassTransformationImpl(
1106: targetObjectCtClass, _contextClassLoader, log, null);
1107:
1108: ct.removeField("_fieldToRemove");
1109:
1110: ct.finish();
1111:
1112: Class transformed = _classPool.toClass(targetObjectCtClass,
1113: _loader);
1114:
1115: for (Field f : transformed.getDeclaredFields()) {
1116: if (f.getName().equals("_fieldToRemove"))
1117: throw new AssertionError(
1118: "_fieldToRemove still in transformed class.");
1119: }
1120:
1121: verify();
1122: }
1123:
1124: @Test
1125: public void get_method_identifier() throws Exception {
1126: Log log = mockLog();
1127:
1128: replay();
1129:
1130: ClassTransformation ct = createClassTransformation(
1131: MethodIdentifier.class, log);
1132:
1133: List<MethodSignature> sigs = ct
1134: .findMethodsWithAnnotation(OnEvent.class);
1135:
1136: assertEquals(sigs.size(), 1);
1137:
1138: MethodSignature sig = sigs.get(0);
1139:
1140: assertEquals(
1141: ct.getMethodIdentifier(sig),
1142: "org.apache.tapestry.internal.transform.pages.MethodIdentifier.makeWaves(java.lang.String, int[]) (at MethodIdentifier.java:24)");
1143:
1144: verify();
1145: }
1146: }
|