    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}