// Copyright 2006, 2007, 2010, 2011, 2012 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.apache.tapestry5.ioc.internal.services; import org.apache.tapestry5.ioc.services.*; import org.apache.tapestry5.plastic.*; import java.lang.reflect.Method; public class PropertyShadowBuilderImpl implements PropertyShadowBuilder { private final PropertyAccess propertyAccess; private final PlasticProxyFactory proxyFactory; public PropertyShadowBuilderImpl(@Builtin PlasticProxyFactory proxyFactory, PropertyAccess propertyAccess) { this.proxyFactory = proxyFactory; this.propertyAccess = propertyAccess; } @Override public <T> T build(final Object source, final String propertyName, final Class<T> propertyType) { final Class sourceClass = source.getClass(); final PropertyAdapter adapter = propertyAccess.getAdapter(sourceClass).getPropertyAdapter(propertyName); // TODO: Perhaps extend ClassPropertyAdapter to do these checks? if (adapter == null) throw new RuntimeException(ServiceMessages.noSuchProperty(sourceClass, propertyName)); if (!adapter.isRead()) { throw new RuntimeException( String.format("Class %s does not provide an accessor ('getter') method for property '%s'.", source.getClass().getName(), propertyName)); } if (!propertyType.isAssignableFrom(adapter.getType())) throw new RuntimeException(ServiceMessages.propertyTypeMismatch(propertyName, sourceClass, adapter.getType(), propertyType)); ClassInstantiator instantiator = proxyFactory.createProxy(propertyType, new PlasticClassTransformer() { @Override public void transform(PlasticClass plasticClass) { final PlasticField sourceField = plasticClass.introduceField(sourceClass, "source").inject(source); PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(propertyType.getName(), "readProperty", null, null); // You don't do this using MethodAdvice, because then we'd have to use reflection to access the read // method. delegateMethod.changeImplementation(new InstructionBuilderCallback() { @Override public void doBuild(InstructionBuilder builder) { builder.loadThis().getField(sourceField); builder.invoke(sourceClass, propertyType, adapter.getReadMethod().getName()); // Now add the null check. builder.dupe().when(Condition.NULL, new InstructionBuilderCallback() { @Override public void doBuild(InstructionBuilder builder) { builder.throwException( NullPointerException.class, String.format( "Unable to delegate method invocation to property '%s' of %s, because the property is null.", propertyName, source)); } }); builder.returnResult(); } }); for (Method m : propertyType.getMethods()) { plasticClass.introduceMethod(m).delegateTo(delegateMethod); } plasticClass.addToString(String.format("<Shadow: property %s of %s>", propertyName, source)); } }); return propertyType.cast(instantiator.newInstance()); } }