001// Copyright 2009, 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 015package org.apache.tapestry5.ioc.internal.services; 016 017import java.lang.reflect.Method; 018import java.util.Map; 019 020import org.apache.tapestry5.ioc.ObjectCreator; 021import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 022import org.apache.tapestry5.ioc.internal.util.InternalUtils; 023import org.apache.tapestry5.ioc.services.Builtin; 024import org.apache.tapestry5.ioc.services.PlasticProxyFactory; 025import org.apache.tapestry5.ioc.services.ThunkCreator; 026import org.apache.tapestry5.plastic.ClassInstantiator; 027import org.apache.tapestry5.plastic.InstructionBuilder; 028import org.apache.tapestry5.plastic.InstructionBuilderCallback; 029import org.apache.tapestry5.plastic.PlasticClass; 030import org.apache.tapestry5.plastic.PlasticClassTransformer; 031import org.apache.tapestry5.plastic.PlasticField; 032import org.apache.tapestry5.plastic.PlasticMethod; 033import org.apache.tapestry5.plastic.PlasticUtils; 034 035@SuppressWarnings("all") 036public class ThunkCreatorImpl implements ThunkCreator 037{ 038 private final Map<Class, ClassInstantiator> interfaceToInstantiator = CollectionFactory.newConcurrentMap(); 039 040 private final PlasticProxyFactory proxyFactory; 041 042 private static final Method CREATE_OBJECT = PlasticUtils.getMethod(ObjectCreator.class, "createObject"); 043 044 public ThunkCreatorImpl(@Builtin 045 PlasticProxyFactory proxyFactory) 046 { 047 this.proxyFactory = proxyFactory; 048 } 049 050 public <T> T createThunk(Class<T> proxyType, ObjectCreator objectCreator, String description) 051 { 052 assert proxyType != null; 053 assert objectCreator != null; 054 assert InternalUtils.isNonBlank(description); 055 056 if (!proxyType.isInterface()) 057 throw new IllegalArgumentException(String.format( 058 "Thunks may only be created for interfaces; %s is a class.", 059 PlasticUtils.toTypeName(proxyType))); 060 061 return getInstantiator(proxyType).with(ObjectCreator.class, objectCreator).with(String.class, description) 062 .newInstance(); 063 064 } 065 066 private <T> ClassInstantiator<T> getInstantiator(Class<T> interfaceType) 067 { 068 ClassInstantiator<T> result = interfaceToInstantiator.get(interfaceType); 069 070 if (result == null) 071 { 072 result = createInstantiator(interfaceType); 073 interfaceToInstantiator.put(interfaceType, result); 074 } 075 076 return result; 077 } 078 079 private <T> ClassInstantiator<T> createInstantiator(final Class<T> interfaceType) 080 { 081 return proxyFactory.createProxy(interfaceType, new PlasticClassTransformer() 082 { 083 public void transform(PlasticClass plasticClass) 084 { 085 final PlasticField objectCreatorField = plasticClass.introduceField(ObjectCreator.class, "creator") 086 .injectFromInstanceContext(); 087 088 PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(interfaceType.getName(), "delegate", 089 null, null); 090 091 delegateMethod.changeImplementation(new InstructionBuilderCallback() 092 { 093 public void doBuild(InstructionBuilder builder) 094 { 095 builder.loadThis().getField(objectCreatorField); 096 builder.invoke(CREATE_OBJECT); 097 builder.checkcast(interfaceType).returnResult(); 098 } 099 }); 100 101 for (Method method : interfaceType.getMethods()) 102 { 103 plasticClass.introduceMethod(method).delegateTo(delegateMethod); 104 } 105 106 if (!plasticClass.isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 107 { 108 final PlasticField descriptionField = plasticClass.introduceField(String.class, "description") 109 .injectFromInstanceContext(); 110 111 plasticClass.introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 112 { 113 public void doBuild(InstructionBuilder builder) 114 { 115 builder.loadThis().getField(descriptionField).returnResult(); 116 } 117 }); 118 } 119 } 120 }); 121 } 122}