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; 024 025public class PropertyShadowBuilderImpl implements PropertyShadowBuilder 026{ 027 private final PropertyAccess propertyAccess; 028 029 private final PlasticProxyFactory proxyFactory; 030 031 public PropertyShadowBuilderImpl(@Builtin 032 PlasticProxyFactory proxyFactory, 033 034 PropertyAccess propertyAccess) 035 { 036 this.proxyFactory = proxyFactory; 037 this.propertyAccess = propertyAccess; 038 } 039 040 @Override 041 public <T> T build(final Object source, final String propertyName, final Class<T> propertyType) 042 { 043 final Class sourceClass = source.getClass(); 044 final PropertyAdapter adapter = propertyAccess.getAdapter(sourceClass).getPropertyAdapter(propertyName); 045 046 // TODO: Perhaps extend ClassPropertyAdapter to do these checks? 047 048 if (adapter == null) 049 throw new RuntimeException(ServiceMessages.noSuchProperty(sourceClass, propertyName)); 050 051 if (!adapter.isRead()) 052 { 053 throw new RuntimeException( 054 String.format("Class %s does not provide an accessor ('getter') method for property '%s'.", 055 source.getClass().getName(), propertyName)); 056 } 057 058 if (!propertyType.isAssignableFrom(adapter.getType())) 059 throw new RuntimeException(ServiceMessages.propertyTypeMismatch(propertyName, sourceClass, 060 adapter.getType(), propertyType)); 061 062 ClassInstantiator instantiator = proxyFactory.createProxy(propertyType, new PlasticClassTransformer() 063 { 064 @Override 065 public void transform(PlasticClass plasticClass) 066 { 067 final PlasticField sourceField = plasticClass.introduceField(sourceClass, "source").inject(source); 068 069 PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(propertyType.getName(), 070 "readProperty", null, null); 071 072 // You don't do this using MethodAdvice, because then we'd have to use reflection to access the read 073 // method. 074 075 delegateMethod.changeImplementation(new InstructionBuilderCallback() 076 { 077 @Override 078 public void doBuild(InstructionBuilder builder) 079 { 080 builder.loadThis().getField(sourceField); 081 builder.invoke(sourceClass, propertyType, adapter.getReadMethod().getName()); 082 083 // Now add the null check. 084 085 builder.dupe().when(Condition.NULL, new InstructionBuilderCallback() 086 { 087 @Override 088 public void doBuild(InstructionBuilder builder) 089 { 090 builder.throwException( 091 NullPointerException.class, 092 String.format( 093 "Unable to delegate method invocation to property '%s' of %s, because the property is null.", 094 propertyName, source)); 095 } 096 }); 097 098 builder.returnResult(); 099 } 100 }); 101 102 for (Method m : propertyType.getMethods()) 103 { 104 plasticClass.introduceMethod(m).delegateTo(delegateMethod); 105 } 106 107 plasticClass.addToString(String.format("<Shadow: property %s of %s>", propertyName, source)); 108 } 109 }); 110 111 return propertyType.cast(instantiator.newInstance()); 112 } 113}