    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    }