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