001    // Copyright 2006, 2007, 2010, 2011 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.tapestry5.ioc.internal.services;
016    
017    import java.lang.reflect.Method;
018    import java.lang.reflect.Modifier;
019    
020    import org.apache.tapestry5.ioc.services.Builtin;
021    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
022    import org.apache.tapestry5.ioc.services.PropertyAccess;
023    import org.apache.tapestry5.ioc.services.PropertyAdapter;
024    import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
025    import org.apache.tapestry5.plastic.ClassInstantiator;
026    import org.apache.tapestry5.plastic.Condition;
027    import org.apache.tapestry5.plastic.WhenCallback;
028    import org.apache.tapestry5.plastic.InstructionBuilder;
029    import org.apache.tapestry5.plastic.InstructionBuilderCallback;
030    import org.apache.tapestry5.plastic.MethodDescription;
031    import org.apache.tapestry5.plastic.PlasticClass;
032    import org.apache.tapestry5.plastic.PlasticClassTransformer;
033    import org.apache.tapestry5.plastic.PlasticField;
034    import org.apache.tapestry5.plastic.PlasticMethod;
035    
036    public class PropertyShadowBuilderImpl implements PropertyShadowBuilder
037    {
038        private final PropertyAccess propertyAccess;
039    
040        private final PlasticProxyFactory proxyFactory;
041    
042        public PropertyShadowBuilderImpl(@Builtin
043        PlasticProxyFactory proxyFactory,
044    
045        PropertyAccess propertyAccess)
046        {
047            this.proxyFactory = proxyFactory;
048            this.propertyAccess = propertyAccess;
049        }
050    
051        public <T> T build(final Object source, final String propertyName, final Class<T> propertyType)
052        {
053            final Class sourceClass = source.getClass();
054            final PropertyAdapter adapter = propertyAccess.getAdapter(sourceClass).getPropertyAdapter(propertyName);
055    
056            // TODO: Perhaps extend ClassPropertyAdapter to do these checks?
057    
058            if (adapter == null)
059                throw new RuntimeException(ServiceMessages.noSuchProperty(sourceClass, propertyName));
060    
061            if (!adapter.isRead())
062                throw new RuntimeException(ServiceMessages.readNotSupported(source, propertyName));
063    
064            if (!propertyType.isAssignableFrom(adapter.getType()))
065                throw new RuntimeException(ServiceMessages.propertyTypeMismatch(propertyName, sourceClass,
066                        adapter.getType(), propertyType));
067    
068            ClassInstantiator instantiator = proxyFactory.createProxy(propertyType, new PlasticClassTransformer()
069            {
070                public void transform(PlasticClass plasticClass)
071                {
072                    final PlasticField sourceField = plasticClass.introduceField(sourceClass, "source").inject(source);
073    
074                    PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(propertyType.getName(),
075                            "readProperty", null, null);
076    
077                    // You don't do this using MethodAdvice, because then we'd have to use reflection to access the read
078                    // method.
079    
080                    delegateMethod.changeImplementation(new InstructionBuilderCallback()
081                    {
082                        public void doBuild(InstructionBuilder builder)
083                        {
084                            builder.loadThis().getField(sourceField);
085                            builder.invoke(sourceClass, propertyType, adapter.getReadMethod().getName());
086    
087                            // Now add the null check.
088    
089                            builder.dupe().when(Condition.NULL, new InstructionBuilderCallback()
090                            {
091                                public void doBuild(InstructionBuilder builder)
092                                {
093                                    builder.throwException(
094                                            NullPointerException.class,
095                                            String.format(
096                                                    "Unable to delegate method invocation to property '%s' of %s, because the property is null.",
097                                                    propertyName, source));
098                                }
099                            });
100    
101                            builder.returnResult();
102                        }
103                    });
104    
105                    for (Method m : propertyType.getMethods())
106                    {
107                        plasticClass.introduceMethod(m).delegateTo(delegateMethod);
108                    }
109    
110                    plasticClass.addToString(String.format("<Shadow: property %s of %s>", propertyName, source));
111                }
112            });
113    
114            return propertyType.cast(instantiator.newInstance());
115        }
116    }