    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;
    021import java.lang.reflect.Modifier;
    022import java.util.ArrayList;
    023import java.util.Arrays;
    024import java.util.Collections;
    025import java.util.Comparator;
    026import java.util.Iterator;
    027import java.util.List;
    028
    029public class PropertyShadowBuilderImpl implements PropertyShadowBuilder
    030{
    031    private final PropertyAccess propertyAccess;
    032
    033    private final PlasticProxyFactory proxyFactory;
    034
    035    final private static MethodSignatureUniqueComparator METHOD_COMPARATOR = new MethodSignatureUniqueComparator();
    036
    037    public PropertyShadowBuilderImpl(@Builtin
    038                                     PlasticProxyFactory proxyFactory,
    039
    040                                     PropertyAccess propertyAccess)
    041    {
    042        this.proxyFactory = proxyFactory;
    043        this.propertyAccess = propertyAccess;
    044    }
    045
    046    @Override
    047    public <T> T build(final Object source, final String propertyName, final Class<T> propertyType)
    048    {
    049        final Class sourceClass = source.getClass();
    050        final PropertyAdapter adapter = propertyAccess.getAdapter(sourceClass).getPropertyAdapter(propertyName);
    051
    052        // TODO: Perhaps extend ClassPropertyAdapter to do these checks?
    053
    054        if (adapter == null)
    055            throw new RuntimeException(ServiceMessages.noSuchProperty(sourceClass, propertyName));
    056
    057        if (!adapter.isRead())
    058        {
    059            throw new RuntimeException(
    060                    String.format("Class %s does not provide an accessor ('getter') method for property '%s'.",
    061                            source.getClass().getName(), propertyName));
    062        }
    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            @Override
    071            public void transform(PlasticClass plasticClass)
    072            {
    073                final PlasticField sourceField = plasticClass.introduceField(sourceClass, "source").inject(source);
    074
    075                PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(propertyType.getName(),
    076                        "readProperty", null, null);
    077
    078                // You don't do this using MethodAdvice, because then we'd have to use reflection to access the read
    079                // method.
    080
    081                delegateMethod.changeImplementation(new InstructionBuilderCallback()
    082                {
    083                    @Override
    084                    public void doBuild(InstructionBuilder builder)
    085                    {
    086                        builder.loadThis().getField(sourceField);
    087                        builder.invoke(sourceClass, propertyType, adapter.getReadMethod().getName());
    088
    089                        // Now add the null check.
    090
    091                        builder.dupe().when(Condition.NULL, new InstructionBuilderCallback()
    092                        {
    093                            @Override
    094                            public void doBuild(InstructionBuilder builder)
    095                            {
    096                                builder.throwException(
    097                                        NullPointerException.class,
    098                                        String.format(
    099                                                "Unable to delegate method invocation to property '%s' of %s, because the property is null.",
    100                                                propertyName, source));
    101                            }
    102                        });
    103
    104                        builder.returnResult();
    105                    }
    106                });
    107
    108                for (Method m : METHOD_COMPARATOR.getUniqueMethods(propertyType))
    109                {
    110                    final MethodDescription description = new MethodDescription(m);
    111                    if (Modifier.isStatic(description.modifiers)) {
    112                        continue;
    113                    }
    114                    plasticClass.introduceMethod(description).delegateTo(delegateMethod);
    115                }
    116
    117                plasticClass.addToString(String.format("<Shadow: property %s of %s>", propertyName, source));
    118            }
    119        });
    120
    121        return propertyType.cast(instantiator.newInstance());
    122    }
    123
    124    private final static class MethodSignatureUniqueComparator implements Comparator<Method> {
    125
    126        @Override
    127        public int compare(Method o1, Method o2) {
    128
    129            int comparison = o1.getName().compareTo(o2.getName());
    130
    131            if (comparison == 0) {
    132                comparison = o1.getParameterTypes().length - o2.getParameterTypes().length;
    133            }
    134
    135            if (comparison == 0) {
    136                final int count = o1.getParameterTypes().length;
    137                for (int i = 0; i < count; i++) {
    138                    Class p1 = o1.getParameterTypes()[i];
    139                    Class p2 = o2.getParameterTypes()[i];
    140                    if (!p1.equals(p2)) {
    141                        comparison = p1.getName().compareTo(p2.getName());
    142                        break;
    143                    }
    144                }
    145            }
    146
    147            return comparison;
    148        }
    149
    150        public List<Method> getUniqueMethods(Class interfaceType)
    151        {
    152            final List<Method> unique = new ArrayList<>(Arrays.asList(interfaceType.getMethods()));
    153            Collections.sort(unique, this);
    154            Method last = null;
    155            Iterator<Method> iterator = unique.iterator();
    156            while (iterator.hasNext())
    157            {
    158                Method m = iterator.next();
    159                if (last != null && compare(m, last) == 0)
    160                {
    161                    iterator.remove();
    162                }
    163                last = m;
    164            }
    165            return unique;
    166        }
    167
    168    }
    169}