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