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}