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}