/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho.specmodels.generator; import javax.lang.model.element.Modifier; import com.facebook.litho.annotations.FromEvent; import com.facebook.litho.annotations.Param; import com.facebook.litho.specmodels.model.ClassNames; import com.facebook.litho.specmodels.model.EventDeclarationModel; import com.facebook.litho.specmodels.model.EventMethodModel; import com.facebook.litho.specmodels.model.MethodParamModel; import com.facebook.litho.specmodels.model.MethodParamModelUtils; import com.facebook.litho.specmodels.model.SpecModel; import com.facebook.litho.specmodels.model.SpecModelUtils; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import static com.facebook.litho.specmodels.generator.ComponentImplGenerator.getImplAccessor; import static com.facebook.litho.specmodels.generator.GeneratorConstants.ABSTRACT_IMPL_PARAM_NAME; import static com.facebook.litho.specmodels.generator.GeneratorConstants.IMPL_VARIABLE_NAME; import static com.facebook.litho.specmodels.model.ClassNames.EVENT_HANDLER; import static com.facebook.litho.specmodels.model.ClassNames.OBJECT; /** * Class that generates the event methods for a Component. */ public class EventGenerator { private EventGenerator() { } public static TypeSpecDataHolder generate(SpecModel specModel) { final TypeSpecDataHolder.Builder builder = TypeSpecDataHolder.newBuilder() .addTypeSpecDataHolder(generateGetEventHandlerMethods(specModel)) .addTypeSpecDataHolder(generateEventDispatchers(specModel)) .addTypeSpecDataHolder(generateEventMethods(specModel)) .addTypeSpecDataHolder(generateEventHandlerFactories(specModel)); if (!specModel.getEventMethods().isEmpty()) { builder.addMethod(generateDispatchOnEvent(specModel)); } return builder.build(); } static TypeSpecDataHolder generateGetEventHandlerMethods(SpecModel specModel) { final TypeSpecDataHolder.Builder dataHolder = TypeSpecDataHolder.newBuilder(); for (EventDeclarationModel eventDeclaration : specModel.getEventDeclarations()) { dataHolder.addTypeSpecDataHolder(generateGetEventHandlerMethod(specModel, eventDeclaration)); } return dataHolder.build(); } static TypeSpecDataHolder generateGetEventHandlerMethod( SpecModel specModel, EventDeclarationModel eventDeclaration) { final String scopeMethodName = "getComponentScope"; return TypeSpecDataHolder.newBuilder() .addMethod( MethodSpec.methodBuilder("get" + eventDeclaration.name.simpleName() + "Handler") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(ClassNames.EVENT_HANDLER) .addParameter(specModel.getContextClass(), "context") .addCode( CodeBlock.builder() .beginControlFlow("if (context.$L() == null)", scopeMethodName) .addStatement("return null") .endControlFlow() .build()) .addStatement( "return (($L.$L) context.$L()).$L", specModel.getComponentName(), ComponentImplGenerator.getImplClassName(specModel), scopeMethodName, ComponentImplGenerator.getEventHandlerInstanceName(eventDeclaration.name)) .build()) .build(); } static TypeSpecDataHolder generateEventDispatchers(SpecModel specModel) { final TypeSpecDataHolder.Builder dataHolder = TypeSpecDataHolder.newBuilder(); for (EventDeclarationModel eventDeclaration : specModel.getEventDeclarations()) { dataHolder.addTypeSpecDataHolder(generateEventDispatcher(eventDeclaration)); } return dataHolder.build(); } static TypeSpecDataHolder generateEventDispatcher(EventDeclarationModel eventDeclaration) { MethodSpec.Builder eventDispatcherMethod = MethodSpec.methodBuilder("dispatch" + eventDeclaration.name.simpleName()) .addModifiers(Modifier.STATIC) .addParameter(ClassNames.EVENT_HANDLER, "_eventHandler"); eventDispatcherMethod.addStatement( "$T _eventState = new $T()", eventDeclaration.name, // need to make these into types eventDeclaration.name); for (EventDeclarationModel.FieldModel fieldModel : eventDeclaration.fields) { if (fieldModel.field.modifiers.contains(Modifier.FINAL)) { continue; } eventDispatcherMethod .addParameter(fieldModel.field.type, fieldModel.field.name) .addStatement("_eventState.$L = $L", fieldModel.field.name, fieldModel.field.name); } eventDispatcherMethod.addStatement( "$T _lifecycle = _eventHandler.mHasEventDispatcher.getEventDispatcher()", ClassNames.EVENT_DISPATCHER); if (eventDeclaration.returnType.equals(TypeName.VOID)) { eventDispatcherMethod.addStatement("_lifecycle.dispatchOnEvent(_eventHandler, _eventState)"); } else { eventDispatcherMethod .addStatement( "return ($L) _lifecycle.dispatchOnEvent(_eventHandler, _eventState)", eventDeclaration.returnType) .returns(eventDeclaration.returnType); } return TypeSpecDataHolder.newBuilder().addMethod(eventDispatcherMethod.build()).build(); } static TypeSpecDataHolder generateEventMethods(SpecModel specModel) { final TypeSpecDataHolder.Builder typeSpecDataHolder = TypeSpecDataHolder.newBuilder(); for (EventMethodModel eventMethod : specModel.getEventMethods()) { typeSpecDataHolder.addMethod(generateEventMethod(specModel, eventMethod)); } return typeSpecDataHolder.build(); } /** * Generate a delegate to the Spec that defines this event method. */ static MethodSpec generateEventMethod( SpecModel specModel, EventMethodModel eventMethodModel) { final String implName = specModel.getComponentName() + "Impl"; final MethodSpec.Builder methodSpec = MethodSpec.methodBuilder(eventMethodModel.name.toString()) .addModifiers(Modifier.PRIVATE) .returns(eventMethodModel.returnType) .addParameter(ClassNames.HAS_EVENT_DISPATCHER_CLASSNAME, ABSTRACT_IMPL_PARAM_NAME) .addStatement( "$L $L = ($L) $L", implName, IMPL_VARIABLE_NAME, implName, ABSTRACT_IMPL_PARAM_NAME); final CodeBlock.Builder delegation = CodeBlock.builder(); final String sourceDelegateAccessor = SpecModelUtils.getSpecAccessor(specModel); if (eventMethodModel.returnType.equals(TypeName.VOID)) { delegation.add("$L.$L(\n", sourceDelegateAccessor, eventMethodModel.name); } else { delegation.add( "$T _result = ($T) $L.$L(\n", eventMethodModel.returnType, eventMethodModel.returnType, sourceDelegateAccessor, eventMethodModel.name); } delegation.indent(); for (int i = 0, size = eventMethodModel.methodParams.size(); i < size; i++) { final MethodParamModel methodParamModel = eventMethodModel.methodParams.get(i); if (MethodParamModelUtils.isAnnotatedWith(methodParamModel, FromEvent.class) || MethodParamModelUtils.isAnnotatedWith(methodParamModel, Param.class) || methodParamModel.getType().equals(specModel.getContextClass())) { methodSpec.addParameter(methodParamModel.getType(), methodParamModel.getName()); delegation.add(methodParamModel.getName()); } else { delegation.add( "($T) $L.$L", methodParamModel.getType(), IMPL_VARIABLE_NAME, getImplAccessor(specModel, methodParamModel)); } if (i < eventMethodModel.methodParams.size() - 1) { delegation.add(",\n"); } else { delegation.add(");\n"); } } delegation.unindent(); methodSpec.addCode(delegation.build()); if (!eventMethodModel.returnType.equals(TypeName.VOID)) { methodSpec.addStatement("return _result"); } return methodSpec.build(); } /** * Generate a dispatchOnEvent() implementation for the component. */ static MethodSpec generateDispatchOnEvent(SpecModel specModel) { final MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("dispatchOnEvent") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .returns(TypeName.OBJECT) .addParameter( ParameterSpec.builder(EVENT_HANDLER, "eventHandler", Modifier.FINAL) .build()) .addParameter( ParameterSpec.builder(OBJECT, "eventState", Modifier.FINAL).build()); methodBuilder.addStatement("int id = eventHandler.id"); methodBuilder.beginControlFlow("switch($L)", "id"); for (EventMethodModel eventMethodModel : specModel.getEventMethods()) { methodBuilder.beginControlFlow("case $L:", eventMethodModel.name.toString().hashCode()); final String eventVariableName = "_event"; methodBuilder.addStatement( "$T $L = ($T) $L", eventMethodModel.eventType.name, eventVariableName, eventMethodModel.eventType.name, "eventState"); final CodeBlock.Builder eventHandlerParams = CodeBlock.builder() .indent() .add("\n$L", "eventHandler.mHasEventDispatcher"); int paramIndex = 0; for (MethodParamModel methodParamModel : eventMethodModel.methodParams) { if (MethodParamModelUtils.isAnnotatedWith(methodParamModel, FromEvent.class)) { eventHandlerParams.add(",\n$L.$L", eventVariableName, methodParamModel.getName()); } else if (MethodParamModelUtils.isAnnotatedWith(methodParamModel, Param.class) || methodParamModel.getType().equals(specModel.getContextClass())) { eventHandlerParams.add( ",\n($T) eventHandler.params[$L]", methodParamModel.getType(), paramIndex++); } } eventHandlerParams.unindent(); if (!eventMethodModel.returnType.equals(TypeName.VOID)) { methodBuilder.addStatement( "return $L($L)", eventMethodModel.name, eventHandlerParams.build()); } else { methodBuilder.addStatement( "$L($L)", eventMethodModel.name, eventHandlerParams.build()); methodBuilder.addStatement("return null"); } methodBuilder.endControlFlow(); } return methodBuilder.addStatement("default:\nreturn null") .endControlFlow() .build(); } static TypeSpecDataHolder generateEventHandlerFactories(SpecModel specModel) { final TypeSpecDataHolder.Builder typeSpecDataHolder = TypeSpecDataHolder.newBuilder(); for (EventMethodModel eventMethodModel : specModel.getEventMethods()) { typeSpecDataHolder.addMethod( generateEventHandlerFactory(eventMethodModel, specModel.getContextClass())); typeSpecDataHolder.addMethod( generateEventHandlerFactory(eventMethodModel, specModel.getComponentClass())); } return typeSpecDataHolder.build(); } static MethodSpec generateEventHandlerFactory( EventMethodModel eventMethodModel, TypeName paramClass) { final MethodSpec.Builder builder = MethodSpec.methodBuilder(eventMethodModel.name.toString()) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(paramClass, "c") .returns(ParameterizedTypeName.get( ClassNames.EVENT_HANDLER, eventMethodModel.eventType.name)); final CodeBlock.Builder paramsBlock = CodeBlock.builder(); paramsBlock.add("new Object[] {\n"); paramsBlock.indent(); paramsBlock.add("c,\n"); for (MethodParamModel methodParamModel : eventMethodModel.methodParams) { if (MethodParamModelUtils.isAnnotatedWith(methodParamModel, Param.class)) { builder.addParameter(methodParamModel.getType(), methodParamModel.getName()); paramsBlock.add("$L,\n", methodParamModel.getName()); if (methodParamModel.getType() instanceof TypeVariableName) { builder.addTypeVariable((TypeVariableName) methodParamModel.getType()); } } } paramsBlock.unindent(); paramsBlock.add("}"); builder.addStatement( "return newEventHandler(c, $L, $L)", eventMethodModel.name.toString().hashCode(), paramsBlock.build()); return builder.build(); } }