    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}