001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.enhance;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.hivemind.ClassResolver;
019    import org.apache.hivemind.Location;
020    import org.apache.hivemind.impl.DefaultClassResolver;
021    import org.apache.hivemind.service.BodyBuilder;
022    import org.apache.hivemind.service.ClassFab;
023    import org.apache.hivemind.service.ClassFactory;
024    import org.apache.hivemind.service.MethodSignature;
025    import org.apache.hivemind.service.impl.ClassFactoryImpl;
026    import org.apache.tapestry.*;
027    import org.apache.tapestry.components.Insert;
028    import org.apache.tapestry.event.PageDetachListener;
029    import org.apache.tapestry.event.PageValidateListener;
030    import org.apache.tapestry.link.ServiceLink;
031    import org.apache.tapestry.services.ComponentConstructor;
032    import org.apache.tapestry.spec.IComponentSpecification;
033    import static org.easymock.EasyMock.*;
034    import org.testng.annotations.BeforeMethod;
035    import org.testng.annotations.Test;
036    
037    import java.lang.reflect.Modifier;
038    import java.util.HashMap;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.Set;
042    
043    /**
044     * Tests for {@link org.apache.tapestry.enhance.EnhancementOperationImpl}.
045     * 
046     * @author Howard M. Lewis Ship
047     * @since 4.0
048     */
049    @Test(sequential=true)
050    public class TestEnhancementOperation extends BaseComponentTestCase
051    {
052        @BeforeMethod(alwaysRun=true)
053        protected void setUp() throws Exception
054        {
055            EnhancementOperationImpl._uid = 97;
056        }
057    
058        public abstract static class Fixture
059        {
060            public abstract String getStringProperty();
061    
062            public abstract boolean isBooleanProperty();
063    
064            public abstract boolean getFlagProperty();
065        }
066    
067        public abstract static class ValidatingComponent extends AbstractComponent implements
068                PageValidateListener
069        {
070        }
071    
072        public abstract static class GetClassReferenceFixture
073        {
074            public abstract Class getClassReference();
075        }
076    
077        public static class MissingConstructorFixture
078        {
079            public MissingConstructorFixture(String foo)
080            {
081                //
082            }
083        }
084    
085        public abstract static class UnclaimedAbstractPropertiesFixture
086        {
087            public abstract String getReadOnly();
088    
089            public abstract void setWriteOnly(String value);
090    
091            public void setConcrete(int i)
092            {
093                //
094            }
095    
096            public int getConcrete()
097            {
098                return -1;
099            }
100        }
101    
102        public void test_Claimed_Property()
103        {
104            EnhancementOperationImpl eo = new EnhancementOperationImpl();
105    
106            eo.claimProperty("foo");
107            eo.claimProperty("bar");
108    
109            try
110            {
111                eo.claimProperty("foo");
112                unreachable();
113            }
114            catch (ApplicationRuntimeException ex)
115            {
116                assertEquals(EnhanceMessages.claimedProperty("foo"), ex.getMessage());
117            }
118        }
119    
120        public void test_Claim_Readonly_Property_Does_Not_Exist()
121        {
122            IComponentSpecification spec = newSpec();
123            ClassFactory cf = newClassFactory();
124    
125            replay();
126    
127            EnhancementOperationImpl eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
128                    BaseComponent.class, cf, null);
129            
130            assertFalse(eo.canClaimAsReadOnlyProperty("foo"));
131            
132            eo.claimReadonlyProperty("foo");
133            
134            assertFalse(eo.canClaimAsReadOnlyProperty("foo"));
135            
136            verify();
137        }
138    
139        public void test_Claim_Readonly_Property_Claimed()
140        {
141            IComponentSpecification spec = newSpec();
142            ClassFactory cf = newClassFactory();
143    
144            replay();
145    
146            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
147                    BaseComponent.class, cf, null);
148    
149            eo.claimReadonlyProperty("foo");
150            eo.claimReadonlyProperty("bar");
151    
152            try
153            {
154                eo.claimProperty("foo");
155                unreachable();
156            }
157            catch (ApplicationRuntimeException ex)
158            {
159                assertEquals(EnhanceMessages.claimedProperty("foo"), ex.getMessage());
160            }
161    
162            verify();
163        }
164    
165        public void test_Claim_Readonly_Property_Has_Setter()
166        {
167            IComponentSpecification spec = newSpec();
168            ClassFactory cf = newClassFactory();
169    
170            replay();
171    
172            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
173                    BaseComponent.class, cf, null);
174    
175            try
176            {
177                // id is a read/write property (even if it isn't abstract)
178                eo.claimReadonlyProperty("id");
179                unreachable();
180            }
181            catch (ApplicationRuntimeException ex)
182            {
183                assertEquals(
184                        "Property id should be read-only; remove method public void org.apache.tapestry.AbstractComponent.setId(java.lang.String).",
185                        ex.getMessage());
186            }
187    
188            verify();
189        }
190    
191        private ClassFactory newClassFactory()
192        {
193            return newClassFactory(BaseComponent.class);
194        }
195    
196        private ClassFactory newClassFactory(Class baseClass)
197        {
198            return newClassFactory(baseClass, newClassFab());
199        }
200    
201        private ClassFactory newClassFactory(Class baseClass, ClassFab classFab)
202        {
203            ClassFactory factory = newMock(ClassFactory.class);
204    
205            String className = baseClass.getName();
206            int dotx = className.lastIndexOf('.');
207            String baseName = className.substring(dotx + 1);
208    
209            expect(factory.newClass(startsWith("$" + baseName), eq(baseClass))).andReturn(classFab);
210    
211            return factory;
212        }
213    
214        private ClassFab newClassFab()
215        {
216            return newMock(ClassFab.class);
217        }
218    
219        public void test_Constructor_And_Accessors()
220        {
221            IComponentSpecification spec = newSpec();
222            ClassFactory cf = newClassFactory();
223    
224            replay();
225    
226            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
227                    BaseComponent.class, cf, null);
228    
229            assertSame(BaseComponent.class, eo.getBaseClass());
230    
231            verify();
232        }
233    
234        public void test_Check_Implements_No_Interface()
235        {
236            IComponentSpecification spec = newSpec();
237            ClassFactory cf = newClassFactory();
238    
239            replay();
240    
241            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
242                    BaseComponent.class, cf, null);
243    
244            assertEquals(false, eo.implementsInterface(PageValidateListener.class));
245    
246            verify();
247        }
248    
249        public void test_Check_Implements_Class_Implements()
250        {
251            IComponentSpecification spec = newSpec();
252            ClassFactory cf = newClassFactory(ValidatingComponent.class);
253    
254            replay();
255    
256            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
257                    ValidatingComponent.class, cf, null);
258    
259            assertEquals(true, eo.implementsInterface(PageValidateListener.class));
260    
261            verify();
262        }
263    
264        public void testCheckImplementsNoMatchForAddedInterfaces()
265        {
266            IComponentSpecification spec = newSpec();
267            ClassFactory factory = newMock(ClassFactory.class);
268            ClassFab classfab = newMock(ClassFab.class);
269    
270            expect(factory.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(classfab);
271    
272            classfab.addInterface(PageDetachListener.class);
273    
274            replay();
275    
276            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
277                    BaseComponent.class, factory, null);
278    
279            eo.extendMethodImplementation(
280                    PageDetachListener.class,
281                    EnhanceUtils.PAGE_DETACHED_SIGNATURE,
282                    "foo();");
283    
284            assertEquals(false, eo.implementsInterface(PageValidateListener.class));
285    
286            verify();
287        }
288    
289        public void testCheckImplementsMatchAddedInterfaces()
290        {
291            IComponentSpecification spec = newSpec();
292    
293            ClassFactory factory = newMock(ClassFactory.class);
294            ClassFab classfab = newMock(ClassFab.class);
295    
296            expect(factory.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(classfab);
297    
298            classfab.addInterface(PageDetachListener.class);
299    
300            replay();
301    
302            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
303                    BaseComponent.class, factory, null);
304    
305            eo.extendMethodImplementation(
306                    PageDetachListener.class,
307                    EnhanceUtils.PAGE_DETACHED_SIGNATURE,
308                    "foo();");
309    
310            assertEquals(true, eo.implementsInterface(PageDetachListener.class));
311    
312            verify();
313        }
314    
315        public void testValidatePropertyNew()
316        {
317            IComponentSpecification spec = newSpec();
318            ClassFactory cf = newClassFactory();
319    
320            replay();
321    
322            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
323                    BaseComponent.class, cf, null);
324    
325            // Validates because BaseComponent doesn't have such a property.
326    
327            eo.validateProperty("abraxis", Set.class);
328    
329            verify();
330        }
331    
332        public void testValidatePropertyMatches()
333        {
334            IComponentSpecification spec = newSpec();
335            ClassFactory cf = newClassFactory();
336    
337            replay();
338    
339            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
340                    BaseComponent.class, cf, null);
341    
342            // Validates because BaseComponent inherits a page property of type IPage
343    
344            eo.validateProperty("page", IPage.class);
345    
346            verify();
347        }
348    
349        public void testValidatePropertyMismatch()
350        {
351            IComponentSpecification spec = newSpec();
352            ClassFactory cf = newClassFactory();
353    
354            replay();
355    
356            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
357                    BaseComponent.class, cf, null);
358    
359            // Validates because BaseComponent inherits a page property of type IPage
360    
361            try
362            {
363                eo.validateProperty("page", String.class);
364                unreachable();
365            }
366            catch (ApplicationRuntimeException ex)
367            {
368                assertEquals(EnhanceMessages.propertyTypeMismatch(
369                        BaseComponent.class,
370                        "page",
371                        IPage.class,
372                        String.class), ex.getMessage());
373            }
374    
375            verify();
376        }
377    
378        public void testConvertTypeName()
379        {
380            IComponentSpecification spec = newSpec();
381            ClassFactory cf = newClassFactory();
382    
383            replay();
384    
385            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
386                    BaseComponent.class, cf, null);
387    
388            assertSame(boolean.class, eo.convertTypeName("boolean"));
389            assertSame(float[].class, eo.convertTypeName("float[]"));
390            assertSame(double[][].class, eo.convertTypeName("double[][]"));
391    
392            assertSame(Set.class, eo.convertTypeName(Set.class.getName()));
393    
394            assertSame(List[].class, eo.convertTypeName(List.class.getName() + "[]"));
395    
396            try
397            {
398                eo.convertTypeName("package.DoesNotExist");
399                unreachable();
400            }
401            catch (ApplicationRuntimeException ex)
402            {
403                // Ignore message, it's from HiveMind anyway.
404            }
405    
406            verify();
407    
408        }
409    
410        public void testAddField()
411        {
412            IComponentSpecification spec = newSpec();
413    
414            ClassFactory cf = newMock(ClassFactory.class);
415            ClassFab fab = newMock(ClassFab.class);
416    
417            expect(cf.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(fab);
418    
419            fab.addField("fred", String.class);
420    
421            replay();
422    
423            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
424                    BaseComponent.class, cf, null);
425    
426            eo.addField("fred", String.class);
427    
428            verify();
429        }
430    
431        public void testAddInjectedField()
432        {
433            IComponentSpecification spec = newSpec();
434            
435            ClassFactory cf = newMock(ClassFactory.class);
436            ClassFab fab = newMock(ClassFab.class);
437            
438            expect(cf.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(fab);
439            
440            // String because "FRED_VALUE" is a String
441    
442            fab.addField("fred", String.class);
443            
444            replay();
445            
446            EnhancementOperationImpl eo = new EnhancementOperationImpl(new DefaultClassResolver(),
447                    spec, BaseComponent.class, cf, null);
448    
449            assertEquals("fred", eo.addInjectedField("fred", String.class, "FRED_VALUE"));
450    
451            verify();
452    
453            HashMap map = new HashMap();
454    
455            fab.addField("fred_0", Map.class);
456            
457            replay();
458    
459            assertEquals("fred_0", eo.addInjectedField("fred", Map.class, map));
460    
461            verify();
462    
463            BodyBuilder body = new BodyBuilder();
464            body.begin();
465            body.addln("fred = $1;");
466            body.addln("fred_0 = $2;");
467            body.end();
468            
469            fab.addConstructor(aryEq(new Class[] { String.class, Map.class }), (Class[])isNull(), 
470                    eq(body.toString()));
471            
472            replay();
473    
474            eo.finalizeEnhancedClass();
475    
476            verify();
477        }
478    
479        public void testAddMethod()
480        {
481            Location l = newLocation();
482    
483            Class baseClass = Insert.class;
484            MethodSignature sig = new MethodSignature(void.class, "frob", null, null);
485    
486            IComponentSpecification spec = newSpec();
487    
488            ClassFactory cf = newMock(ClassFactory.class);
489            ClassFab fab = newMock(ClassFab.class);
490    
491            // We force the uid to 97 in setUp()
492    
493            expect(cf.newClass(startsWith("$Insert"), eq(baseClass))).andReturn(fab);
494            
495            expect(fab.addMethod(Modifier.PUBLIC, sig, "method body")).andReturn(null);
496    
497            replay();
498    
499            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
500                    baseClass, cf, null);
501    
502            eo.addMethod(Modifier.PUBLIC, sig, "method body", l);
503    
504            verify();
505        }
506    
507        public void testAddMethodDuplicate()
508        {
509            Location firstLocation = newLocation();
510            Location secondLocation = newLocation();
511    
512            Class baseClass = Insert.class;
513            MethodSignature sig = new MethodSignature(void.class, "frob", null, null);
514    
515            IComponentSpecification spec = newSpec();
516    
517            ClassFactory cf = newMock(ClassFactory.class);
518            ClassFab fab = newMock(ClassFab.class);
519    
520            // We force the uid to 97 in setUp()
521    
522            expect(cf.newClass(startsWith("$Insert"), eq(baseClass))).andReturn(fab);
523    
524            expect(fab.addMethod(Modifier.PUBLIC, sig, "method body")).andReturn(null);
525    
526            replay();
527    
528            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
529                    baseClass, cf, null);
530    
531            eo.addMethod(Modifier.PUBLIC, sig, "method body", firstLocation);
532    
533            try
534            {
535                eo.addMethod(Modifier.PUBLIC, sig, "second method body", secondLocation);
536                unreachable();
537            }
538            catch (ApplicationRuntimeException ex)
539            {
540                assertTrue(ex.getMessage()
541                        .indexOf("A new implementation of method 'void frob()' conflicts with an existing "
542                                + "implementation") > -1);
543                
544                assertSame(secondLocation, ex.getLocation());
545            }
546    
547            verify();
548        }
549    
550        public void testGetAccessorMethodName()
551        {
552            IComponentSpecification spec = newSpec();
553            ClassFactory cf = newClassFactory(Fixture.class);
554    
555            replay();
556    
557            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
558                    Fixture.class, cf, null);
559    
560            assertEquals("getStringProperty", eo.getAccessorMethodName("stringProperty"));
561            assertEquals("isBooleanProperty", eo.getAccessorMethodName("booleanProperty"));
562            assertEquals("getFlagProperty", eo.getAccessorMethodName("flagProperty"));
563            assertEquals("getUnknownProperty", eo.getAccessorMethodName("unknownProperty"));
564    
565            verify();
566        }
567    
568        /**
569         * On this test, instead of mocking up everything, we actually use the raw implementations to
570         * construct a new class; the class gets a class reference passed to it in its constructor.
571         */
572    
573        public void test_Get_Class_Reference() throws Exception
574        {
575            Location l = newLocation();
576            IComponentSpecification spec = newSpec();
577    
578            expect(spec.getLocation()).andReturn(l);
579    
580            replay();
581    
582            EnhancementOperationImpl eo = new EnhancementOperationImpl(new DefaultClassResolver(),
583                    spec, GetClassReferenceFixture.class, new ClassFactoryImpl(), null);
584    
585            // This does two things; it creates a new field, and it sets up a new constructor
586            // parameter to inject the class value (Map.class) into each new instance.
587    
588            String ref = eo.getClassReference(Map.class);
589            String ref2 = eo.getClassReference(Map.class);
590    
591            eo.addMethod(Modifier.PUBLIC, new MethodSignature(Class.class, "getClassReference", null,
592                    null), "return " + ref + ";", l);
593    
594            ComponentConstructor cc = eo.getConstructor();
595    
596            GetClassReferenceFixture f = (GetClassReferenceFixture) cc.newInstance();
597    
598            assertSame(Map.class, f.getClassReference());
599    
600            verify();
601    
602            assertSame(ref, ref2);
603        }
604    
605        public void testGetArrayClassReference() throws Exception
606        {
607            IComponentSpecification spec = newSpec();
608    
609            replay();
610    
611            EnhancementOperationImpl eo = new EnhancementOperationImpl(new DefaultClassResolver(),
612                    spec, GetClassReferenceFixture.class, new ClassFactoryImpl(), null);
613    
614            String ref = eo.getClassReference(int[].class);
615    
616            assertTrue(ref.indexOf('[') < 0);
617    
618            verify();
619        }
620    
621        /**
622         * Really a test for {@link org.apache.tapestry.enhance.ComponentConstructorImpl};
623         * {@link #test_Get_Class_Reference()} tests the success case, just want to fill in the failure.
624         */
625    
626        public void testComponentConstructorFailure()
627        {
628            Location l = newLocation();
629    
630            ComponentConstructor cc = new ComponentConstructorImpl(BaseComponent.class
631                    .getConstructors()[0], new Object[]
632            { "unexpected" }, "<classfab>", l);
633    
634            try
635            {
636                cc.newInstance();
637                unreachable();
638            }
639            catch (ApplicationRuntimeException ex)
640            {
641                assertExceptionSubstring(
642                        ex,
643                        "Unable to instantiate instance of class org.apache.tapestry.BaseComponent");
644                assertSame(l, ex.getLocation());
645            }
646        }
647    
648        public void test_Get_Property_Type()
649        {
650            IComponentSpecification spec = newSpec();
651            ClassFactory cf = newClassFactory();
652    
653            replay();
654    
655            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec,
656                    BaseComponent.class, cf, null);
657    
658            assertEquals(Map.class, eo.getPropertyType("assets"));
659            assertEquals(IPage.class, eo.getPropertyType("page"));
660    
661            // Doesn't exist, so returns null
662    
663            assertNull(eo.getPropertyType("foosball"));
664    
665            verify();
666        }
667    
668        public void test_Find_Unclaimed_Abstract_Properties()
669        {
670            ClassResolver cr = newMock(ClassResolver.class);
671            IComponentSpecification spec = newSpec();
672            ClassFactory cf = newClassFactory(UnclaimedAbstractPropertiesFixture.class);
673    
674            replay();
675    
676            EnhancementOperation eo = new EnhancementOperationImpl(cr, spec,
677                    UnclaimedAbstractPropertiesFixture.class, cf, null);
678    
679            List l = eo.findUnclaimedAbstractProperties();
680    
681            assertEquals(2, l.size());
682            assertEquals(true, l.contains("readOnly"));
683            assertEquals(true, l.contains("writeOnly"));
684    
685            eo.claimProperty("readOnly");
686    
687            l = eo.findUnclaimedAbstractProperties();
688    
689            assertEquals(1, l.size());
690            assertEquals(true, l.contains("writeOnly"));
691    
692            eo.claimProperty("writeOnly");
693    
694            l = eo.findUnclaimedAbstractProperties();
695    
696            assertEquals(true, l.isEmpty());
697    
698            verify();
699        }
700    
701        public void testGetNewMethod()
702        {
703            ClassResolver cr = new DefaultClassResolver();
704            
705            IComponentSpecification spec = newSpec();
706            
707            ClassFactory cf = newMock(ClassFactory.class);
708            
709            ClassFab fab = newMock(ClassFab.class);
710    
711            expect(cf.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(fab);
712    
713            fab.addInterface(PageDetachListener.class);
714            
715            replay();
716    
717            EnhancementOperationImpl eo = new EnhancementOperationImpl(cr, spec, BaseComponent.class,
718                    cf, null);
719    
720            MethodSignature sig = EnhanceUtils.PAGE_DETACHED_SIGNATURE;
721    
722            eo.extendMethodImplementation(PageDetachListener.class, sig, "some-code();");
723    
724            verify();
725    
726            replay();
727    
728            // Check that repeated calls do not
729            // keep adding methods.
730    
731            eo.extendMethodImplementation(PageDetachListener.class, sig, "more-code();");
732    
733            verify();
734    
735            expect(fab.addMethod(Modifier.PUBLIC, sig, "{\n  some-code();\n  more-code();\n}\n")).andReturn(null);
736    
737            expect(fab.createClass()).andReturn(BaseComponent.class);
738    
739            expect(spec.getLocation()).andReturn(null);
740    
741            replay();
742    
743            eo.getConstructor();
744    
745            verify();
746        }
747    
748        public void testGetExistingMethod()
749        {
750            ClassResolver cr = new DefaultClassResolver();
751            
752            IComponentSpecification spec = newSpec();
753    
754            ClassFactory cf = newMock(ClassFactory.class);
755            
756            ClassFab fab = newMock(ClassFab.class);
757    
758            expect(cf.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(fab);
759    
760            replay();
761    
762            EnhancementOperationImpl eo = new EnhancementOperationImpl(cr, spec, BaseComponent.class,
763                    cf, null);
764    
765            MethodSignature sig = EnhanceUtils.FINISH_LOAD_SIGNATURE;
766    
767            eo.extendMethodImplementation(IComponent.class, sig, "some-code();");
768    
769            verify();
770    
771            expect(fab.addMethod(Modifier.PUBLIC, sig, "{\n  super.finishLoad($$);\n  some-code();\n}\n"))
772            .andReturn(null);
773    
774            expect(fab.createClass()).andReturn(BaseComponent.class);
775    
776            expect(spec.getLocation()).andReturn(null);
777    
778            replay();
779    
780            eo.getConstructor();
781    
782            verify();
783        }
784    
785        public void testGetExistingProtectedMethod()
786        {
787            ClassResolver cr = new DefaultClassResolver();
788            IComponentSpecification spec = newSpec();
789    
790            ClassFactory cf = newMock(ClassFactory.class);
791            
792            ClassFab fab = newMock(ClassFab.class);
793    
794            expect(cf.newClass(startsWith("$BaseComponent"), eq(BaseComponent.class))).andReturn(fab);
795    
796            replay();
797    
798            EnhancementOperationImpl eo = new EnhancementOperationImpl(cr, spec, BaseComponent.class,
799                    cf, null);
800    
801            // A protected method
802            MethodSignature sig = EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE;
803    
804            eo.extendMethodImplementation(IComponent.class, sig, "some-code();");
805    
806            verify();
807    
808            expect(fab.addMethod(
809                    Modifier.PUBLIC,
810                    sig,
811                    "{\n  super.cleanupAfterRender($$);\n  some-code();\n}\n"))
812                    .andReturn(null);
813    
814            expect(fab.createClass()).andReturn(BaseComponent.class);
815    
816            expect(spec.getLocation()).andReturn(null);
817    
818            replay();
819    
820            eo.getConstructor();
821    
822            verify();
823        }
824    
825        public static abstract class ExistingAbstractMethodFixture extends BaseComponent implements
826                PageDetachListener
827        {
828            //
829        }
830    
831        public void getExistingAbstractMethod()
832        {
833            ClassResolver cr = new DefaultClassResolver();
834            IComponentSpecification spec = newSpec();
835    
836            ClassFactory cf = newMock(ClassFactory.class);
837            
838            ClassFab fab = newMock(ClassFab.class);
839    
840            expect(cf.newClass(startsWith("$TestEnhancementOperation$ExistingAbstractMethodFixture"), eq(TestEnhancementOperation.ExistingAbstractMethodFixture.class)))
841            .andReturn(fab);
842            
843            replay();
844    
845            EnhancementOperationImpl eo = new EnhancementOperationImpl(cr, spec,
846                    ExistingAbstractMethodFixture.class, cf, null);
847    
848            MethodSignature sig = EnhanceUtils.PAGE_DETACHED_SIGNATURE;
849    
850            eo.extendMethodImplementation(PageDetachListener.class, sig, "some-code();");
851    
852            verify();
853    
854            expect(fab.addMethod(Modifier.PUBLIC, sig, "{\n  some-code();\n}\n")).andReturn(null);
855    
856            expect(fab.createClass()).andReturn(BaseComponent.class);
857    
858            expect(spec.getLocation()).andReturn(null);
859    
860            replay();
861    
862            eo.getConstructor();
863    
864            verify();
865        }
866    
867        /**
868         * This seems to pass on the command line, but fail inside Eclipse. I think Eclipse's Java
869         * compiler works a little different from Java's ... in this example (TODO: Create test fixture
870         * classes for this test) the getTarget() method doesn't show up as a declared public method
871         * when Eclipse compiles the code, but does when JDK compiles the code.
872         */
873        public void testPropertyInheritedFromInterface()
874        {
875            IComponentSpecification spec = newSpec();
876            ClassFactory cf = newClassFactory(ServiceLink.class);
877    
878            replay();
879            
880            EnhancementOperation eo = new EnhancementOperationImpl(new DefaultClassResolver(), spec, ServiceLink.class, cf, null);
881    
882            assertEquals(String.class, eo.getPropertyType("target"));
883    
884            verify();
885        }
886    
887        public void testConstructorFailure()
888        {
889            IComponentSpecification spec = newSpec();
890            
891            ClassFab classFab = newMock(ClassFab.class);
892    
893            ClassFactory cf = newClassFactory(ServiceLink.class, classFab);
894            
895            Throwable t = new RuntimeException("Inconceivable!");
896    
897            expect(classFab.createClass()).andThrow(t);
898            
899            replay();
900            
901            EnhancementOperationImpl eo = new EnhancementOperationImpl(new DefaultClassResolver(),
902                    spec, ServiceLink.class, cf, null);
903    
904            try
905            {
906                eo.getConstructor();
907                unreachable();
908            }
909            catch (ApplicationRuntimeException ex)
910            {
911                assertEquals(
912                        "Failure enhancing class org.apache.tapestry.link.ServiceLink: Inconceivable!",
913                        ex.getMessage());
914                assertSame(classFab, ex.getComponent());
915                assertSame(t, ex.getRootCause());
916            }
917    
918            verify();
919        }
920        
921    }