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
015package org.apache.tapestry5.ioc.internal.services;
016
017import java.lang.reflect.Method;
018import java.lang.reflect.Modifier;
019
020import org.apache.tapestry5.ioc.services.Builtin;
021import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
022import org.apache.tapestry5.ioc.services.PropertyAccess;
023import org.apache.tapestry5.ioc.services.PropertyAdapter;
024import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
025import org.apache.tapestry5.plastic.ClassInstantiator;
026import org.apache.tapestry5.plastic.Condition;
027import org.apache.tapestry5.plastic.WhenCallback;
028import org.apache.tapestry5.plastic.InstructionBuilder;
029import org.apache.tapestry5.plastic.InstructionBuilderCallback;
030import org.apache.tapestry5.plastic.MethodDescription;
031import org.apache.tapestry5.plastic.PlasticClass;
032import org.apache.tapestry5.plastic.PlasticClassTransformer;
033import org.apache.tapestry5.plastic.PlasticField;
034import org.apache.tapestry5.plastic.PlasticMethod;
035
036public 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}