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}