001/**
002 * Copyright 2013 John Ericksen
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.androidtransfuse.experiment.generators;
017
018import com.google.common.collect.ImmutableList;
019import com.sun.codemodel.*;
020import org.androidtransfuse.adapter.*;
021import org.androidtransfuse.adapter.classes.ASTClassFactory;
022import org.androidtransfuse.analysis.ManualSuperGenerator;
023import org.androidtransfuse.analysis.astAnalyzer.ListenerAspect;
024import org.androidtransfuse.analysis.astAnalyzer.ManualSuperAspect;
025import org.androidtransfuse.event.SuperCaller;
026import org.androidtransfuse.experiment.*;
027import org.androidtransfuse.gen.ClassGenerationUtil;
028import org.androidtransfuse.gen.InvocationBuilder;
029import org.androidtransfuse.gen.UniqueVariableNamer;
030import org.androidtransfuse.model.InjectionNode;
031import org.androidtransfuse.model.MethodDescriptor;
032import org.androidtransfuse.model.TypedExpression;
033
034import javax.inject.Inject;
035import javax.inject.Named;
036import java.util.*;
037
038public class MethodCallbackGenerator implements Generation {
039
040    private final ASTType eventAnnotation;
041    private final InvocationBuilder invocationBuilder;
042    private final ASTMethod eventMethod;
043    private final ASTMethod creationMethod;
044    private final ASTType superCallerType;
045    private final JCodeModel codeModel;
046    private final UniqueVariableNamer namer;
047    private final ClassGenerationUtil generationUtil;
048
049    @Inject
050    public MethodCallbackGenerator(/*@Assisted*/ ASTType eventAnnotation, /*@Assisted*/ @Named("eventMethod") ASTMethod eventMethod, /*@Assisted */ @Named("creationMethod") ASTMethod creationMethod, InvocationBuilder invocationBuilder, ASTClassFactory astClassFactory, JCodeModel codeModel, UniqueVariableNamer namer, ClassGenerationUtil generationUtil) {
051        this.eventAnnotation = eventAnnotation;
052        this.invocationBuilder = invocationBuilder;
053        this.eventMethod = eventMethod;
054        this.creationMethod = creationMethod;
055        this.codeModel = codeModel;
056        this.namer = namer;
057        this.generationUtil = generationUtil;
058        this.superCallerType = astClassFactory.getType(SuperCaller.class);
059    }
060
061    @Override
062    public void schedule(final ComponentBuilder builder, ComponentDescriptor descriptor) {
063
064        builder.add(creationMethod, GenerationPhase.POSTINJECTION, new ComponentMethodGenerator() {
065            @Override
066            public void generate(MethodDescriptor methodDescriptor, JBlock block) {
067                for (Map.Entry<InjectionNode, TypedExpression> injectionNodeJExpressionEntry : builder.getExpressionMap().entrySet()) {
068                    ListenerAspect methodCallbackAspect = injectionNodeJExpressionEntry.getKey().getAspect(ListenerAspect.class);
069                    final TypedExpression eventReceiverExpression = injectionNodeJExpressionEntry.getValue();
070
071                    if (methodCallbackAspect != null && methodCallbackAspect.contains(eventAnnotation)) {
072                        Set<ASTMethod> methods = methodCallbackAspect.getListeners(eventAnnotation);
073
074                        for (final ASTMethod methodCallback : methods) {
075
076                            final boolean containsSuperCaller = containsSuperCaller(methodCallback.getParameters());
077                            if(containsSuperCaller){
078                                if(!injectionNodeJExpressionEntry.getKey().containsAspect(ManualSuperAspect.class)){
079                                    injectionNodeJExpressionEntry.getKey().addAspect(new ManualSuperAspect());
080                                }
081
082                                ManualSuperAspect manualSuperAspect = injectionNodeJExpressionEntry.getKey().getAspect(ManualSuperAspect.class);
083                                manualSuperAspect.add(eventMethod);
084                            }
085
086                            builder.add(eventMethod, GenerationPhase.EVENT, new ComponentMethodGenerator() {
087                                @Override
088                                public void generate(MethodDescriptor methodDescriptor, JBlock block) {
089
090                                    Map<ASTType, Queue<JExpression>> methodArgumentExpressions = buildMethodArgumentExpressions(methodDescriptor);
091                                    if(containsSuperCaller) {
092                                        methodArgumentExpressions.put(superCallerType, new LinkedList<JExpression>(ImmutableList.of(buildSuperCaller(eventMethod))));
093                                    }
094
095                                    List<JExpression> matchedExpressions = matchMethodArguments(methodCallback.getParameters(), methodArgumentExpressions);
096
097                                    JStatement methodCall = invocationBuilder.buildMethodCall(
098                                            new ASTJDefinedClassType(builder.getDefinedClass()),
099                                            new ASTJDefinedClassType(builder.getDefinedClass()),
100                                            methodCallback,
101                                            matchedExpressions,
102                                            eventReceiverExpression
103                                    );
104
105                                    block.add(methodCall);
106                                }
107                            });
108                        }
109                    }
110                }
111            }
112        });
113    }
114
115    private JExpression buildSuperCaller(ASTMethod eventMethod) {
116
117        JDefinedClass superCallerClass = codeModel.anonymousClass(SuperCaller.class);
118
119        JMethod callMethod = superCallerClass.method(JMod.PUBLIC, Object.class, SuperCaller.CALL_METHOD);
120        JVar objectVarargs = callMethod.varParam(Object.class, namer.generateName(Object.class));
121
122        JInvocation superInvocation = JExpr.ref(ManualSuperGenerator.SUPER_NAME).invoke(eventMethod.getName());
123
124        for(int i = 0; i < eventMethod.getParameters().size(); i++){
125            ASTParameter parameter = eventMethod.getParameters().get(i);
126            superInvocation = superInvocation.arg(JExpr.cast(generationUtil.ref(parameter.getASTType()), objectVarargs.component(JExpr.lit(i))));
127        }
128
129        JBlock body = callMethod.body();
130        if(eventMethod.getReturnType().equals(ASTVoidType.VOID)) {
131            body.add(superInvocation);
132            body._return(JExpr._null());
133        }
134        else{
135            body._return(superInvocation);
136        }
137
138        return JExpr._new(superCallerClass);
139    }
140
141    private boolean containsSuperCaller(ImmutableList<ASTParameter> parameters) {
142
143        for (ASTParameter parameter : parameters) {
144            if(parameter.getASTType().equals(superCallerType)){
145                return true;
146            }
147        }
148        return false;
149    }
150
151    private List<JExpression> matchMethodArguments(List<ASTParameter> parametersToMatch, Map<ASTType, Queue<JExpression>> expressions) {
152        List<JExpression> arguments = new ArrayList<JExpression>();
153
154        for (ASTParameter callParameter : parametersToMatch) {
155            ASTType type = callParameter.getASTType();
156            if(expressions.containsKey(type) && !expressions.get(type).isEmpty()){
157                arguments.add(expressions.get(type).remove());
158            }
159            else{
160                //todo: validation error
161            }
162        }
163
164        return arguments;
165    }
166
167    private Map<ASTType, Queue<JExpression>> buildMethodArgumentExpressions(MethodDescriptor method){
168        Map<ASTType, Queue<JExpression>> argumentExpressions = new HashMap<ASTType, Queue<JExpression>>();
169        for (ASTParameter parameter : method.getASTMethod().getParameters()) {
170            ASTType type = parameter.getASTType();
171            if(!argumentExpressions.containsKey(type)){
172                argumentExpressions.put(type, new LinkedList<JExpression>());
173            }
174
175            argumentExpressions.get(type).add(method.getParameter(parameter).getExpression());
176        }
177        return argumentExpressions;
178    }
179}