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.Array;
018import java.lang.reflect.Method;
019import java.lang.reflect.Modifier;
020import java.util.List;
021
022import org.apache.tapestry5.commons.services.PlasticProxyFactory;
023import org.apache.tapestry5.ioc.services.Builtin;
024import org.apache.tapestry5.ioc.services.ChainBuilder;
025import org.apache.tapestry5.plastic.ClassInstantiator;
026import org.apache.tapestry5.plastic.Condition;
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.WhenCallback;
033
034public class ChainBuilderImpl implements ChainBuilder
035{
036    private final PlasticProxyFactory proxyFactory;
037
038    public ChainBuilderImpl(@Builtin
039    PlasticProxyFactory proxyFactory)
040    {
041        this.proxyFactory = proxyFactory;
042    }
043
044    @Override
045    @SuppressWarnings("unchecked")
046    public <T> T build(final Class<T> commandInterface, List<T> commands)
047    {
048        // Jump through some hoops to convert the list into an array of the proper type
049
050        Object[] array = (Object[]) Array.newInstance(commandInterface, commands.size());
051
052        final Object[] commandsArray = commands.toArray(array);
053
054        ClassInstantiator<T> instantiator = proxyFactory.createProxy(commandInterface, new PlasticClassTransformer()
055        {
056            @Override
057            public void transform(PlasticClass plasticClass)
058            {
059                PlasticField commandsField = plasticClass.introduceField(commandsArray.getClass(), "commands").inject(
060                        commandsArray);
061
062                for (Method method : commandInterface.getMethods())
063                {
064                    if (!Modifier.isStatic(method.getModifiers()))
065                    {
066                        implementMethod(plasticClass, method, commandsField);
067                    }
068                }
069
070                plasticClass.addToString(String.format("<Command chain of %s>", commandInterface.getName()));
071            }
072        });
073
074        return instantiator.newInstance();
075    }
076
077    private void implementMethod(PlasticClass plasticClass, final Method method, final PlasticField commandsField)
078    {
079        plasticClass.introduceMethod(method).changeImplementation(new InstructionBuilderCallback()
080        {
081            @Override
082            public void doBuild(InstructionBuilder builder)
083            {
084                builder.loadThis().getField(commandsField).iterateArray(new InstructionBuilderCallback()
085                {
086                    @Override
087                    public void doBuild(InstructionBuilder builder)
088                    {
089                        // The command is on the stack; add the elements and invoke the method.
090
091                        builder.loadArguments().invoke(method);
092
093                        Class returnType = method.getReturnType();
094
095                        if (returnType == void.class)
096                            return;
097
098                        final boolean wide = returnType == long.class || returnType == double.class;
099
100                        if (wide)
101                            builder.dupeWide();
102                        else
103                            builder.dupe();
104
105                        if (returnType == float.class)
106                        {
107                            builder.loadConstant(0f).compareSpecial("float");
108                        }
109
110                        if (returnType == long.class)
111                        {
112                            builder.loadConstant(0l).compareSpecial("long");
113                        }
114
115                        if (returnType == double.class)
116                        {
117                            builder.loadConstant(0d).compareSpecial("double");
118                        }
119
120                        Condition condition = returnType.isPrimitive() ? Condition.NON_ZERO : Condition.NON_NULL;
121
122                        builder.when(condition, new WhenCallback()
123                        {
124                            @Override
125                            public void ifTrue(InstructionBuilder builder)
126                            {
127                                builder.returnResult();
128                            }
129
130                            @Override
131                            public void ifFalse(InstructionBuilder builder)
132                            {
133                                if (wide)
134                                    builder.popWide();
135                                else
136                                    builder.pop();
137                            }
138                        });
139                    }
140                });
141
142                builder.returnDefaultValue();
143            }
144        });
145    }
146}