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}