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