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