/** * 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 javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.facebook.litho.specmodels.internal.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; /** * Simple implementation of {@link SpecModel}. */ @Immutable public final class SpecModelImpl implements SpecModel { private static final String SPEC_SUFFIX = "Spec"; private final String mSpecName; private final TypeName mSpecTypeName; private final String mComponentName; private final TypeName mComponentTypeName; private final ImmutableList<DelegateMethodModel> mDelegateMethods; private final ImmutableList<EventMethodModel> mEventMethods; private final ImmutableList<UpdateStateMethodModel> mUpdateStateMethods; private final ImmutableList<PropModel> mProps; private final ImmutableList<PropDefaultModel> mPropDefaults; private final ImmutableList<TypeVariableName> mTypeVariables; private final ImmutableList<StateParamModel> mStateValues; private final ImmutableList<InterStageInputParamModel> mInterStageInputs; private final ImmutableList<TreePropModel> mTreeProps; private final ImmutableList<EventDeclarationModel> mEventDeclarations; private final String mClassJavadoc; private final ImmutableList<PropJavadocModel> mPropJavadocs; private final boolean mIsPublic; private final boolean mHasInjectedDependencies; @Nullable private final DependencyInjectionHelper mDependencyInjectionHelper; private final Object mRepresentedObject; private SpecModelImpl( String qualifiedSpecClassName, String componentClassName, ImmutableList<DelegateMethodModel> delegateMethods, ImmutableList<EventMethodModel> eventMethods, ImmutableList<UpdateStateMethodModel> updateStateMethods, ImmutableList<TypeVariableName> typeVariables, ImmutableList<PropDefaultModel> propDefaults, ImmutableList<EventDeclarationModel> eventDeclarations, String classJavadoc, ImmutableList<PropJavadocModel> propJavadocs, boolean isPublic, @Nullable DependencyInjectionHelper dependencyInjectionHelper, Object representedObject) { mSpecName = getSpecName(qualifiedSpecClassName); mSpecTypeName = ClassName.bestGuess(qualifiedSpecClassName); mComponentName = getComponentName(componentClassName, qualifiedSpecClassName); mComponentTypeName = getComponentTypeName(componentClassName, qualifiedSpecClassName); mDelegateMethods = delegateMethods; mEventMethods = eventMethods; mUpdateStateMethods = updateStateMethods; mProps = getProps(delegateMethods, eventMethods, updateStateMethods); mPropDefaults = propDefaults; mTypeVariables = typeVariables; mStateValues = getStateValues(delegateMethods, eventMethods, updateStateMethods); mInterStageInputs = getInterStageInputs(delegateMethods, eventMethods, updateStateMethods); mTreeProps = getTreeProps(delegateMethods, eventMethods, updateStateMethods); mEventDeclarations = eventDeclarations; mClassJavadoc = classJavadoc; mPropJavadocs = propJavadocs; mIsPublic = isPublic; mHasInjectedDependencies = dependencyInjectionHelper != null; mDependencyInjectionHelper = dependencyInjectionHelper; mRepresentedObject = representedObject; } @Override public String getSpecName() { return mSpecName; } @Override public TypeName getSpecTypeName() { return mSpecTypeName; } @Override public String getComponentName() { return mComponentName; } @Override public TypeName getComponentTypeName() { return mComponentTypeName; } @Override public ImmutableList<DelegateMethodModel> getDelegateMethods() { return mDelegateMethods; } @Override public ImmutableList<EventMethodModel> getEventMethods() { return mEventMethods; } @Override public ImmutableList<UpdateStateMethodModel> getUpdateStateMethods() { return mUpdateStateMethods; } @Override public ImmutableList<PropModel> getProps() { return mProps; } @Override public ImmutableList<PropDefaultModel> getPropDefaults() { return mPropDefaults; } @Override public ImmutableList<TypeVariableName> getTypeVariables() { return mTypeVariables; } @Override public ImmutableList<StateParamModel> getStateValues() { return mStateValues; } @Override public ImmutableList<InterStageInputParamModel> getInterStageInputs() { return mInterStageInputs; } @Override public ImmutableList<TreePropModel> getTreeProps() { return mTreeProps; } @Override public ImmutableList<EventDeclarationModel> getEventDeclarations() { return mEventDeclarations; } @Override public String getClassJavadoc() { return mClassJavadoc; } @Override public ImmutableList<PropJavadocModel> getPropJavadocs() { return mPropJavadocs; } @Override public boolean isPublic() { return mIsPublic; } @Override public ClassName getContextClass() { throw new RuntimeException("Don't delegate to this method!"); } @Override public ClassName getComponentClass() { throw new RuntimeException("Don't delegate to this method!"); } @Override public ClassName getStateContainerClass() { throw new RuntimeException("Don't delegate to this method!"); } @Override public TypeName getUpdateStateInterface() { throw new RuntimeException("Don't delegate to this method!"); } @Override public boolean isStylingSupported() { throw new RuntimeException("Don't delegate to this method!"); } @Override public boolean hasInjectedDependencies() { return mHasInjectedDependencies; } @Nullable @Override public DependencyInjectionHelper getDependencyInjectionHelper() { return mDependencyInjectionHelper; } @Override public Object getRepresentedObject() { return mRepresentedObject; } @Override public List<SpecModelValidationError> validate() { throw new RuntimeException("Don't delegate to this method!"); } @Override public TypeSpec generate() { throw new RuntimeException("Don't delegate to this method!"); } private static String getSpecName(String qualifiedSpecClassName) { return qualifiedSpecClassName.substring(qualifiedSpecClassName.lastIndexOf('.') + 1); } private static ClassName getComponentTypeName( String componentClassName, String qualifiedSpecClassName) { final String qualifiedComponentClassName; if (componentClassName == null || componentClassName.isEmpty()) { qualifiedComponentClassName = qualifiedSpecClassName.substring( 0, qualifiedSpecClassName.length() - SPEC_SUFFIX.length()); } else { qualifiedComponentClassName = qualifiedSpecClassName.substring(0, qualifiedSpecClassName.lastIndexOf('.') + 1) + componentClassName; } return ClassName.bestGuess(qualifiedComponentClassName); } private static String getComponentName(String componentClassName, String qualifiedSpecClassName) { return getComponentTypeName(componentClassName, qualifiedSpecClassName).simpleName(); } private static ImmutableList<PropModel> getProps( ImmutableList<DelegateMethodModel> delegateMethods, ImmutableList<EventMethodModel> eventMethods, ImmutableList<UpdateStateMethodModel> updateStateMethods) { final Set<PropModel> props = new LinkedHashSet<>(); for (DelegateMethodModel delegateMethod : delegateMethods) { for (MethodParamModel param : delegateMethod.methodParams) { if (param instanceof PropModel) { props.add((PropModel) param); } } } for (EventMethodModel eventMethod : eventMethods) { for (MethodParamModel param : eventMethod.methodParams) { if (param instanceof PropModel) { props.add((PropModel) param); } } } for (UpdateStateMethodModel updateStateMethod : updateStateMethods) { for (MethodParamModel param : updateStateMethod.methodParams) { if (param instanceof PropModel) { props.add((PropModel) param); } } } return ImmutableList.copyOf(new ArrayList<>(props)); } private static ImmutableList<StateParamModel> getStateValues( ImmutableList<DelegateMethodModel> delegateMethods, ImmutableList<EventMethodModel> eventMethods, ImmutableList<UpdateStateMethodModel> updateStateMethods) { final Set<StateParamModel> stateValues = new LinkedHashSet<>(); for (DelegateMethodModel delegateMethod : delegateMethods) { for (MethodParamModel param : delegateMethod.methodParams) { if (param instanceof StateParamModel) { stateValues.add((StateParamModel) param); } } } for (EventMethodModel eventMethod : eventMethods) { for (MethodParamModel param : eventMethod.methodParams) { if (param instanceof StateParamModel) { stateValues.add((StateParamModel) param); } } } for (UpdateStateMethodModel updateStateMethod : updateStateMethods) { for (MethodParamModel param : updateStateMethod.methodParams) { if (param instanceof StateParamModel) { stateValues.add((StateParamModel) param); } } } return ImmutableList.copyOf(new ArrayList<>(stateValues)); } private static ImmutableList<InterStageInputParamModel> getInterStageInputs( ImmutableList<DelegateMethodModel> delegateMethods, ImmutableList<EventMethodModel> eventMethods, ImmutableList<UpdateStateMethodModel> updateStateMethods) { final Set<InterStageInputParamModel> interStageInputs = new LinkedHashSet<>(); for (DelegateMethodModel delegateMethod : delegateMethods) { for (MethodParamModel param : delegateMethod.methodParams) { if (param instanceof InterStageInputParamModel) { interStageInputs.add((InterStageInputParamModel) param); } } } for (EventMethodModel eventMethod : eventMethods) { for (MethodParamModel param : eventMethod.methodParams) { if (param instanceof InterStageInputParamModel) { interStageInputs.add((InterStageInputParamModel) param); } } } for (UpdateStateMethodModel updateStateMethod : updateStateMethods) { for (MethodParamModel param : updateStateMethod.methodParams) { if (param instanceof InterStageInputParamModel) { interStageInputs.add((InterStageInputParamModel) param); } } } return ImmutableList.copyOf(new ArrayList<>(interStageInputs)); } private static ImmutableList<TreePropModel> getTreeProps( ImmutableList<DelegateMethodModel> delegateMethods, ImmutableList<EventMethodModel> eventMethods, ImmutableList<UpdateStateMethodModel> updateStateMethods) { final Set<TreePropModel> treeProps = new LinkedHashSet<>(); for (DelegateMethodModel delegateMethod : delegateMethods) { for (MethodParamModel param : delegateMethod.methodParams) { if (param instanceof TreePropModel) { treeProps.add((TreePropModel) param); } } } for (EventMethodModel eventMethod : eventMethods) { for (MethodParamModel param : eventMethod.methodParams) { if (param instanceof TreePropModel) { treeProps.add((TreePropModel) param); } } } for (UpdateStateMethodModel updateStateMethod : updateStateMethods) { for (MethodParamModel param : updateStateMethod.methodParams) { if (param instanceof TreePropModel) { treeProps.add((TreePropModel) param); } } } return ImmutableList.copyOf(new ArrayList<>(treeProps)); } public static Builder newBuilder() { return new Builder(); } public static class Builder { private String mQualifiedSpecClassName; private String mComponentClassName; private ImmutableList<DelegateMethodModel> mDelegateMethodModels; private ImmutableList<EventMethodModel> mEventMethodModels; private ImmutableList<UpdateStateMethodModel> mUpdateStateMethodModels; private ImmutableList<TypeVariableName> mTypeVariableNames; private ImmutableList<PropDefaultModel> mPropDefaultModels; private ImmutableList<EventDeclarationModel> mEventDeclarations; private String mClassJavadoc; private ImmutableList<PropJavadocModel> mPropJavadocs; private boolean mIsPublic; @Nullable private DependencyInjectionHelper mDependencyInjectionHelper; private Object mRepresentedObject; private Builder() { } public Builder qualifiedSpecClassName(String qualifiedSpecClassName) { mQualifiedSpecClassName = qualifiedSpecClassName; return this; } /** * The class name for the generated component. May be unspecified, in which case the spec class * name is used to determine the class name for the component. */ public Builder componentClassName(String componentClassName) { mComponentClassName = componentClassName; return this; } public Builder delegateMethods(ImmutableList<DelegateMethodModel> delegateMethodModels) { mDelegateMethodModels = delegateMethodModels; return this; } public Builder eventMethods(ImmutableList<EventMethodModel> eventMethodModels) { mEventMethodModels = eventMethodModels; return this; } public Builder updateStateMethods( ImmutableList<UpdateStateMethodModel> updateStateMethodModels) { mUpdateStateMethodModels = updateStateMethodModels; return this; } public Builder typeVariables(ImmutableList<TypeVariableName> typeVariableNames) { mTypeVariableNames = typeVariableNames; return this; } public Builder propDefaults(ImmutableList<PropDefaultModel> propDefaultModels) { mPropDefaultModels = propDefaultModels; return this; } public Builder eventDeclarations(ImmutableList<EventDeclarationModel> eventDeclarations) { mEventDeclarations = eventDeclarations; return this; } public Builder classJavadoc(String classJavadoc) { mClassJavadoc = classJavadoc; return this; } public Builder propJavadocs(ImmutableList<PropJavadocModel> propJavadocs) { mPropJavadocs = propJavadocs; return this; } public Builder isPublic(boolean isPublic) { mIsPublic = isPublic; return this; } public Builder dependencyInjectionGenerator( @Nullable DependencyInjectionHelper dependencyInjectionHelper) { mDependencyInjectionHelper = dependencyInjectionHelper; return this; } public Builder representedObject(Object representedObject) { mRepresentedObject = representedObject; return this; } public SpecModelImpl build() { validate(); initFieldsIfNecessary(); return new SpecModelImpl( mQualifiedSpecClassName, mComponentClassName, mDelegateMethodModels, mEventMethodModels, mUpdateStateMethodModels, mTypeVariableNames, mPropDefaultModels, mEventDeclarations, mClassJavadoc, mPropJavadocs, mIsPublic, mDependencyInjectionHelper, mRepresentedObject); } private void validate() { if (mQualifiedSpecClassName == null) { throw new IllegalStateException("Must specify a qualified class name"); } if (mDelegateMethodModels == null) { throw new IllegalStateException("Must specify delegate methods"); } if (mRepresentedObject == null) { throw new IllegalStateException("Must specify represented object"); } } private void initFieldsIfNecessary() { if (mTypeVariableNames == null) { mTypeVariableNames = ImmutableList.of(); } if (mPropDefaultModels == null) { mPropDefaultModels = ImmutableList.of(); } if (mEventMethodModels == null) { mEventMethodModels = ImmutableList.of(); } if (mUpdateStateMethodModels == null) { mUpdateStateMethodModels = ImmutableList.of(); } if (mEventDeclarations == null) { mEventDeclarations = ImmutableList.of(); } } } }