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