001    // Copyright 2009 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 org.apache.tapestry5.ioc.ObjectCreator;
018    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019    import org.apache.tapestry5.ioc.internal.util.Defense;
020    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021    import org.apache.tapestry5.ioc.services.*;
022    
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Modifier;
025    import java.util.Map;
026    
027    public class ThunkCreatorImpl implements ThunkCreator
028    {
029        /**
030         * Map from an interface type to a corresponding "thunk" class that implements the interface.
031         */
032        private final Map<Class, Class> interfaceToThunkClass = CollectionFactory.newConcurrentMap();
033    
034        private final ClassFactory classFactory;
035    
036        private final MethodSignature toStringSignature = new MethodSignature(String.class, "toString", null, null);
037    
038        private static final int PRIVATE_FINAL = Modifier.FINAL + Modifier.PRIVATE;
039    
040        private static final String DESCRIPTION_FIELD = "_$description";
041        private static final String CREATOR_FIELD = "_$creator";
042        private static final String DELEGATE_METHOD = "_$delegate";
043    
044        public ThunkCreatorImpl(@Builtin ClassFactory classFactory)
045        {
046            this.classFactory = classFactory;
047        }
048    
049        public <T> T createThunk(Class<T> proxyType, ObjectCreator objectCreator, String description)
050        {
051            Defense.notNull(proxyType, "proxyType");
052            Defense.notNull(objectCreator, "objectCreator");
053            Defense.notBlank(description, "description");
054    
055            if (!proxyType.isInterface())
056                throw new IllegalArgumentException(
057                        String.format("Thunks may only be created for interfaces; %s is a class.",
058                                      ClassFabUtils.toJavaClassName(proxyType)));
059    
060            final Class thunkClass = getThunkClass(proxyType);
061    
062            Throwable failure;
063    
064            try
065            {
066                return proxyType.cast(thunkClass.getConstructors()[0].newInstance(description, objectCreator));
067            }
068            catch (InvocationTargetException ex)
069            {
070                failure = ex.getTargetException();
071            }
072            catch (Exception ex)
073            {
074                failure = ex;
075            }
076    
077            throw new RuntimeException(String.format("Exception instantiating thunk class %s: %s",
078                                                     thunkClass.getName(),
079                                                     InternalUtils.toMessage(failure)),
080                                       failure);
081        }
082    
083        private Class getThunkClass(Class type)
084        {
085            Class result = interfaceToThunkClass.get(type);
086    
087            if (result == null)
088            {
089                result = constructThunkClass(type);
090                interfaceToThunkClass.put(type, result);
091            }
092    
093            return result;
094        }
095    
096        private Class constructThunkClass(Class interfaceType)
097        {
098            ClassFab classFab = classFactory.newClass(interfaceType);
099    
100            classFab.addField(DESCRIPTION_FIELD, PRIVATE_FINAL, String.class);
101    
102            classFab.addField(CREATOR_FIELD, PRIVATE_FINAL, ObjectCreator.class);
103    
104            classFab.addConstructor(new Class[] { String.class, ObjectCreator.class }, null,
105                                    String.format("{ %s = $1; %s = $2; }", DESCRIPTION_FIELD, CREATOR_FIELD));
106    
107            MethodSignature sig = new MethodSignature(interfaceType, DELEGATE_METHOD, null, null);
108    
109            classFab.addMethod(Modifier.PRIVATE, sig, String.format("return ($r) %s.createObject();", CREATOR_FIELD));
110    
111            MethodIterator mi = new MethodIterator(interfaceType);
112    
113            while (mi.hasNext())
114            {
115                sig = mi.next();
116    
117                classFab.addMethod(Modifier.PUBLIC, sig,
118                                   String.format("return ($r) %s().%s($$);", DELEGATE_METHOD, sig.getName()));
119            }
120    
121            if (!mi.getToString())
122                classFab.addMethod(Modifier.PUBLIC, toStringSignature, String.format("return %s;", DESCRIPTION_FIELD));
123    
124            return classFab.createClass();
125        }
126    }