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 }