package com.airbnb.epoxy; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import static com.airbnb.epoxy.Utils.getElementByName; import static com.airbnb.epoxy.Utils.getEpoxyObjectType; class BasicGeneratedModelInfo extends GeneratedModelInfo { private final Elements elementUtils; BasicGeneratedModelInfo(Types typeUtils, Elements elementUtils, TypeElement superClassElement, ErrorLogger errorLogger) { this.elementUtils = elementUtils; this.superClassName = ParameterizedTypeName.get(superClassElement.asType()); this.superClassElement = superClassElement; generatedClassName = buildGeneratedModelName(superClassElement); for (TypeParameterElement typeParameterElement : superClassElement.getTypeParameters()) { typeVariableNames.add(TypeVariableName.get(typeParameterElement)); } collectOriginalClassConstructors(superClassElement); collectMethodsReturningClassType(superClassElement, typeUtils); if (!typeVariableNames.isEmpty()) { TypeVariableName[] typeArguments = typeVariableNames.toArray(new TypeVariableName[typeVariableNames.size()]); this.parameterizedClassName = ParameterizedTypeName.get(generatedClassName, typeArguments); } else { this.parameterizedClassName = generatedClassName; } TypeMirror boundObjectTypeMirror = getEpoxyObjectType(superClassElement, typeUtils); if (boundObjectTypeMirror == null) { errorLogger .logError("Epoxy model type could not be found. (class: %s)", superClassElement.getSimpleName()); // Return a basic view type so the code can be generated boundObjectTypeMirror = getElementByName(Utils.ANDROID_VIEW_TYPE, elementUtils, typeUtils).asType(); } boundObjectTypeName = TypeName.get(boundObjectTypeMirror); boolean hasEpoxyClassAnnotation = superClassElement.getAnnotation(EpoxyModelClass.class) != null; boolean isAbstract = superClassElement.getModifiers().contains(Modifier.ABSTRACT); // By default we don't extend classes that are abstract; if they don't contain all required // methods then our generated class won't compile. If there is a EpoxyModelClass annotation // though we will always generate the subclass shouldGenerateModel = !isAbstract || hasEpoxyClassAnnotation; } protected ClassName buildGeneratedModelName(TypeElement classElement) { String packageName = elementUtils.getPackageOf(classElement).getQualifiedName().toString(); int packageLen = packageName.length() + 1; String className = classElement.getQualifiedName().toString().substring(packageLen).replace('.', '$'); return ClassName.get(packageName, className + GENERATED_CLASS_NAME_SUFFIX); } /** * Get information about constructors of the original class so we can duplicate them in the * generated class and call through to super with the proper parameters */ private void collectOriginalClassConstructors(TypeElement originalClass) { for (Element subElement : originalClass.getEnclosedElements()) { if (subElement.getKind() == ElementKind.CONSTRUCTOR && !subElement.getModifiers().contains(Modifier.PRIVATE)) { ExecutableElement castedSubElement = ((ExecutableElement) subElement); List<? extends VariableElement> params = castedSubElement.getParameters(); constructors .add(new ConstructorInfo(subElement.getModifiers(), buildParamSpecs(params), castedSubElement.isVarArgs())); } } } }