/**
* Copyright 2011-2015 John Ericksen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.androidtransfuse.experiment.generators;
import com.google.common.collect.ImmutableList;
import com.sun.codemodel.*;
import org.androidtransfuse.adapter.*;
import org.androidtransfuse.adapter.classes.ASTClassFactory;
import org.androidtransfuse.analysis.ManualSuperGenerator;
import org.androidtransfuse.analysis.astAnalyzer.ListenerAspect;
import org.androidtransfuse.analysis.astAnalyzer.ManualSuperAspect;
import org.androidtransfuse.event.SuperCaller;
import org.androidtransfuse.experiment.*;
import org.androidtransfuse.gen.ClassGenerationUtil;
import org.androidtransfuse.gen.InvocationBuilder;
import org.androidtransfuse.gen.UniqueVariableNamer;
import org.androidtransfuse.model.InjectionNode;
import org.androidtransfuse.model.MethodDescriptor;
import org.androidtransfuse.model.TypedExpression;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.*;
public class MethodCallbackGenerator implements Generation {
private final ASTType eventAnnotation;
private final InvocationBuilder invocationBuilder;
private final ASTMethod eventMethod;
private final ASTMethod creationMethod;
private final ASTType superCallerType;
private final JCodeModel codeModel;
private final UniqueVariableNamer namer;
private final ClassGenerationUtil generationUtil;
private final boolean nullDelegateCheck;
@Inject
public MethodCallbackGenerator(/*@Assisted*/ ASTType eventAnnotation, /*@Assisted*/ @Named("eventMethod") ASTMethod eventMethod, /*@Assisted */ @Named("creationMethod") ASTMethod creationMethod, /*@Assisted*/ boolean nullDelegateCheck, InvocationBuilder invocationBuilder, ASTClassFactory astClassFactory, JCodeModel codeModel, UniqueVariableNamer namer, ClassGenerationUtil generationUtil) {
this.eventAnnotation = eventAnnotation;
this.invocationBuilder = invocationBuilder;
this.eventMethod = eventMethod;
this.creationMethod = creationMethod;
this.codeModel = codeModel;
this.namer = namer;
this.generationUtil = generationUtil;
this.superCallerType = astClassFactory.getType(SuperCaller.class);
this.nullDelegateCheck = nullDelegateCheck;
}
@Override
public String getName() {
return "Method Callback Generator @" + eventAnnotation.getPackageClass().getClassName() + " -> " + eventMethod;
}
@Override
public void schedule(final ComponentBuilder builder, ComponentDescriptor descriptor) {
builder.add(creationMethod, GenerationPhase.POSTINJECTION, new ComponentMethodGenerator() {
@Override
public void generate(MethodDescriptor methodDescriptor, JBlock block) {
for (Map.Entry<InjectionNode, TypedExpression> injectionNodeJExpressionEntry : builder.getExpressionMap().entrySet()) {
ListenerAspect methodCallbackAspect = injectionNodeJExpressionEntry.getKey().getAspect(ListenerAspect.class);
final TypedExpression eventReceiverExpression = injectionNodeJExpressionEntry.getValue();
if (methodCallbackAspect != null && methodCallbackAspect.contains(eventAnnotation)) {
Set<ASTMethod> methods = methodCallbackAspect.getListeners(eventAnnotation);
for (final ASTMethod methodCallback : methods) {
final boolean containsSuperCaller = containsSuperCaller(methodCallback.getParameters());
if(containsSuperCaller){
if(!injectionNodeJExpressionEntry.getKey().containsAspect(ManualSuperAspect.class)){
injectionNodeJExpressionEntry.getKey().addAspect(new ManualSuperAspect());
}
ManualSuperAspect manualSuperAspect = injectionNodeJExpressionEntry.getKey().getAspect(ManualSuperAspect.class);
manualSuperAspect.add(eventMethod);
}
builder.add(eventMethod, GenerationPhase.EVENT, new ComponentMethodGenerator() {
@Override
public void generate(MethodDescriptor methodDescriptor, JBlock block) {
Map<ASTType, Queue<JExpression>> methodArgumentExpressions = buildMethodArgumentExpressions(methodDescriptor);
if(containsSuperCaller) {
methodArgumentExpressions.put(superCallerType, new LinkedList<JExpression>(ImmutableList.of(buildSuperCaller(eventMethod))));
}
List<JExpression> matchedExpressions = matchMethodArguments(methodCallback.getParameters(), methodArgumentExpressions);
JStatement methodCall = invocationBuilder.buildMethodCall(
new ASTJDefinedClassType(builder.getDefinedClass()),
new ASTJDefinedClassType(builder.getDefinedClass()),
methodCallback,
matchedExpressions,
eventReceiverExpression
);
if(nullDelegateCheck){
block._if(eventReceiverExpression.getExpression().ne(JExpr._null()))._then().block()
.add(methodCall);
}
else{
block.add(methodCall);
}
}
});
}
}
}
}
});
}
private JExpression buildSuperCaller(ASTMethod eventMethod) {
JDefinedClass superCallerClass = codeModel.anonymousClass(SuperCaller.class);
JMethod callMethod = superCallerClass.method(JMod.PUBLIC, Object.class, SuperCaller.CALL_METHOD);
JVar objectVarargs = callMethod.varParam(Object.class, namer.generateName(Object.class));
JInvocation superInvocation = JExpr.ref(ManualSuperGenerator.SUPER_NAME).invoke(eventMethod.getName());
for(int i = 0; i < eventMethod.getParameters().size(); i++){
ASTParameter parameter = eventMethod.getParameters().get(i);
superInvocation = superInvocation.arg(JExpr.cast(generationUtil.ref(parameter.getASTType()), objectVarargs.component(JExpr.lit(i))));
}
JBlock body = callMethod.body();
if(eventMethod.getReturnType().equals(ASTVoidType.VOID)) {
body.add(superInvocation);
body._return(JExpr._null());
}
else{
body._return(superInvocation);
}
return JExpr._new(superCallerClass);
}
private boolean containsSuperCaller(ImmutableList<ASTParameter> parameters) {
for (ASTParameter parameter : parameters) {
if(parameter.getASTType().equals(superCallerType)){
return true;
}
}
return false;
}
private List<JExpression> matchMethodArguments(List<ASTParameter> parametersToMatch, Map<ASTType, Queue<JExpression>> expressions) {
List<JExpression> arguments = new ArrayList<JExpression>();
for (ASTParameter callParameter : parametersToMatch) {
ASTType type = callParameter.getASTType();
if(expressions.containsKey(type) && !expressions.get(type).isEmpty()){
arguments.add(expressions.get(type).remove());
}
else{
//todo: validation error
}
}
return arguments;
}
private Map<ASTType, Queue<JExpression>> buildMethodArgumentExpressions(MethodDescriptor method){
Map<ASTType, Queue<JExpression>> argumentExpressions = new HashMap<ASTType, Queue<JExpression>>();
for (ASTParameter parameter : method.getASTMethod().getParameters()) {
ASTType type = parameter.getASTType();
if(!argumentExpressions.containsKey(type)){
argumentExpressions.put(type, new LinkedList<JExpression>());
}
argumentExpressions.get(type).add(method.getParameter(parameter).getExpression());
}
return argumentExpressions;
}
}