/** * 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.model; import com.facebook.litho.annotations.FromEvent; import com.facebook.litho.specmodels.internal.ImmutableList; import java.util.ArrayList; import java.util.List; import javax.lang.model.element.Modifier; /** * Class for validating that the event declarations and event methods within a {@link SpecModel} * are well-formed. */ public class EventValidation { static List<SpecModelValidationError> validate(SpecModel specModel) { List<SpecModelValidationError> validationErrors = new ArrayList<>(); validationErrors.addAll(validateEventDeclarations(specModel)); validationErrors.addAll(validateOnEventMethods(specModel)); return validationErrors; } static List<SpecModelValidationError> validateEventDeclarations(SpecModel specModel) { List<SpecModelValidationError> validationErrors = new ArrayList<>(); for (EventDeclarationModel eventDeclaration : specModel.getEventDeclarations()) { if (eventDeclaration.returnType == null) { validationErrors.add( new SpecModelValidationError( eventDeclaration.representedObject, "Event declarations must be annotated with @Event.")); } for (EventDeclarationModel.FieldModel fieldModel : eventDeclaration.fields) { if (!fieldModel.field.modifiers.contains(Modifier.PUBLIC) || (fieldModel.field.modifiers.contains(Modifier.FINAL) && !fieldModel.field.modifiers.contains(Modifier.STATIC))) { validationErrors.add( new SpecModelValidationError( fieldModel.representedObject, "Event fields must be declared as public non-final.")); } } } return validationErrors; } static List<SpecModelValidationError> validateOnEventMethods(SpecModel specModel) { final List<SpecModelValidationError> validationErrors = new ArrayList<>(); final ImmutableList<EventMethodModel> eventMethods = specModel.getEventMethods(); for (int i = 0, size = eventMethods.size(); i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (eventMethods.get(i).name.equals(eventMethods.get(j).name)) { validationErrors.add( new SpecModelValidationError( eventMethods.get(i).representedObject, "Two methods annotated with @OnEvent should not have the same name " + "(" + eventMethods.get(i).name + ").")); } } } for (EventMethodModel eventMethod : eventMethods) { if (!specModel.hasInjectedDependencies() && !eventMethod.modifiers.contains(Modifier.STATIC)) { validationErrors.add( new SpecModelValidationError( eventMethod.representedObject, "Methods in a spec that doesn't have dependency injection must be static.")); } if (!eventMethod.returnType.equals(eventMethod.eventType.returnType)) { validationErrors.add( new SpecModelValidationError( eventMethod.representedObject, "Method must return " + eventMethod.eventType.returnType + " since that is what " + eventMethod.eventType.name + " expects.")); } if (eventMethod.methodParams.isEmpty() || !eventMethod.methodParams.get(0).getType().equals(specModel.getContextClass())) { validationErrors.add( new SpecModelValidationError( eventMethod.representedObject, "The first parameter for a method annotated with @OnEvent should be of type " + specModel.getContextClass() + ".")); } for (MethodParamModel methodParam : eventMethod.methodParams) { if (MethodParamModelUtils.isAnnotatedWith(methodParam, FromEvent.class) && !hasMatchingField(methodParam, eventMethod.eventType.fields)) { validationErrors.add( new SpecModelValidationError( methodParam.getRepresentedObject(), "Param with name " + methodParam.getName() + " and type " + methodParam.getType() + " is not a member of " + eventMethod.eventType.name + ".")); } } } // Need some way of verifying that the return type and the @FromEvent parameters are // correct and valid. return validationErrors; } private static boolean hasMatchingField( MethodParamModel param, ImmutableList<EventDeclarationModel.FieldModel> fields) { for (EventDeclarationModel.FieldModel field : fields) { if (param.getName().equals(field.field.name) && param.getType().equals(field.field.type)) { return true; } } return false; } }