001// Copyright 2006, 2007, 2008, 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 015package org.apache.tapestry5.ioc.internal.services; 016 017import java.lang.reflect.Method; 018import java.util.Map; 019 020import org.apache.tapestry5.commons.services.PlasticProxyFactory; 021import org.apache.tapestry5.commons.util.StrategyRegistry; 022import org.apache.tapestry5.ioc.services.Builtin; 023import org.apache.tapestry5.ioc.services.StrategyBuilder; 024import org.apache.tapestry5.plastic.ClassInstantiator; 025import org.apache.tapestry5.plastic.InstructionBuilder; 026import org.apache.tapestry5.plastic.InstructionBuilderCallback; 027import org.apache.tapestry5.plastic.MethodDescription; 028import org.apache.tapestry5.plastic.PlasticClass; 029import org.apache.tapestry5.plastic.PlasticClassTransformer; 030import org.apache.tapestry5.plastic.PlasticField; 031 032public class StrategyBuilderImpl implements StrategyBuilder 033{ 034 private final PlasticProxyFactory proxyFactory; 035 036 public StrategyBuilderImpl(@Builtin 037 PlasticProxyFactory proxyFactory) 038 { 039 this.proxyFactory = proxyFactory; 040 } 041 042 @Override 043 public <S> S build(StrategyRegistry<S> registry) 044 { 045 return createProxy(registry.getAdapterType(), registry); 046 } 047 048 @Override 049 public <S> S build(Class<S> adapterType, Map<Class, S> registrations) 050 { 051 StrategyRegistry<S> registry = StrategyRegistry.newInstance(adapterType, registrations); 052 053 return build(registry); 054 } 055 056 private <S> S createProxy(final Class<S> interfaceType, final StrategyRegistry<S> registry) 057 { 058 ClassInstantiator instantiator = proxyFactory.createProxy(interfaceType, new PlasticClassTransformer() 059 { 060 @Override 061 public void transform(PlasticClass plasticClass) 062 { 063 final PlasticField registryField = plasticClass.introduceField(StrategyRegistry.class, "registry") 064 .inject(registry); 065 Class<?> interfaceSelectorType = null; 066 067 for (final Method method : interfaceType.getMethods()) 068 { 069 Class<?>[] parameterTypes = method.getParameterTypes(); 070 if (parameterTypes.length == 0) 071 { 072 throw new IllegalArgumentException("Invalid method " + method 073 + ", when using the strategy pattern, every method must take at least the selector as its parameter"); 074 } 075 Class<?> methodSelectorType = parameterTypes[0]; 076 if (interfaceSelectorType == null) 077 { 078 interfaceSelectorType = methodSelectorType; 079 } else if (!interfaceSelectorType.equals(methodSelectorType)) 080 { 081 throw new IllegalArgumentException("Conflicting method definitions," 082 + " expecting the first argument of every method to have the same type"); 083 084 } 085 plasticClass.introduceMethod(new MethodDescription(method), new InstructionBuilderCallback() 086 { 087 @Override 088 public void doBuild(InstructionBuilder builder) 089 { 090 Class returnType = method.getReturnType(); 091 092 builder.loadThis().getField(registryField); 093 094 // Argument 0 is the selector used to find the adapter and should be an object reference, 095 // not a primitive. 096 097 builder.loadArgument(0); 098 099 // Use the StrategyRegistry to get the adapter to re-invoke the method on 100 builder.invoke(StrategyRegistry.class, Object.class, "getByInstance", Object.class) 101 .checkcast(interfaceType); 102 103 // That leaves the correct adapter on top of the stack. Get the 104 // selector and the rest of the arguments in place and invoke the method. 105 106 builder.loadArguments().invoke(interfaceType, returnType, method.getName(), 107 method.getParameterTypes()); 108 109 builder.returnResult(); 110 } 111 }); 112 } 113 114 plasticClass.addToString(String.format("<Strategy for %s>", interfaceType.getName())); 115 } 116 }); 117 118 return interfaceType.cast(instantiator.newInstance()); 119 } 120}