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