package com.airbnb.epoxy;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterSpec.Builder;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
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.VariableElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
abstract class GeneratedModelInfo {
private static final String RESET_METHOD = "reset";
protected static final String GENERATED_CLASS_NAME_SUFFIX = "_";
protected TypeName superClassName;
protected TypeElement superClassElement;
protected TypeName parameterizedClassName;
protected ClassName generatedClassName;
protected TypeName boundObjectTypeName;
protected boolean shouldGenerateModel;
protected final Set<AttributeInfo> attributeInfo = new HashSet<>();
protected final List<TypeVariableName> typeVariableNames = new ArrayList<>();
protected final List<ConstructorInfo> constructors = new ArrayList<>();
protected final Set<MethodInfo> methodsReturningClassType = new LinkedHashSet<>();
/**
* Get information about methods returning class type of the original class so we can duplicate
* them in the generated class for chaining purposes
*/
protected void collectMethodsReturningClassType(TypeElement modelClass, Types typeUtils) {
TypeElement clazz = modelClass;
while (clazz.getSuperclass().getKind() != TypeKind.NONE) {
for (Element subElement : clazz.getEnclosedElements()) {
Set<Modifier> modifiers = subElement.getModifiers();
if (subElement.getKind() == ElementKind.METHOD
&& !modifiers.contains(Modifier.PRIVATE)
&& !modifiers.contains(Modifier.FINAL)
&& !modifiers.contains(Modifier.STATIC)) {
TypeMirror methodReturnType = ((ExecutableType) subElement.asType()).getReturnType();
if (methodReturnType.equals(clazz.asType())
|| typeUtils.isSubtype(clazz.asType(), methodReturnType)) {
ExecutableElement castedSubElement = ((ExecutableElement) subElement);
List<? extends VariableElement> params = castedSubElement.getParameters();
String methodName = subElement.getSimpleName().toString();
if (methodName.equals(RESET_METHOD) && params.isEmpty()) {
continue;
}
methodsReturningClassType.add(new MethodInfo(methodName, modifiers,
buildParamSpecs(params), castedSubElement.isVarArgs()));
}
}
}
clazz = (TypeElement) typeUtils.asElement(clazz.getSuperclass());
}
}
protected List<ParameterSpec> buildParamSpecs(List<? extends VariableElement> params) {
List<ParameterSpec> result = new ArrayList<>();
for (VariableElement param : params) {
Builder builder = ParameterSpec.builder(TypeName.get(param.asType()),
param.getSimpleName().toString());
for (AnnotationMirror annotation : param.getAnnotationMirrors()) {
builder.addAnnotation(AnnotationSpec.get(annotation));
}
result.add(builder.build());
}
return result;
}
void addAttribute(AttributeInfo attributeInfo) {
addAttributes(Collections.singletonList(attributeInfo));
}
void addAttributes(Collection<AttributeInfo> attributeInfo) {
removeMethodIfDuplicatedBySetter(attributeInfo);
this.attributeInfo.addAll(attributeInfo);
}
private void removeMethodIfDuplicatedBySetter(Collection<AttributeInfo> attributeInfos) {
for (AttributeInfo attributeInfo : attributeInfos) {
Iterator<MethodInfo> iterator = methodsReturningClassType.iterator();
while (iterator.hasNext()) {
MethodInfo methodInfo = iterator.next();
if (methodInfo.name.equals(attributeInfo.getName())
// checking for overloads
&& methodInfo.params.size() == 1
&& methodInfo.params.get(0).type.equals(attributeInfo.getTypeName())) {
iterator.remove();
}
}
}
}
TypeElement getSuperClassElement() {
return superClassElement;
}
TypeName getSuperClassName() {
return superClassName;
}
List<ConstructorInfo> getConstructors() {
return constructors;
}
Set<MethodInfo> getMethodsReturningClassType() {
return methodsReturningClassType;
}
ClassName getGeneratedName() {
return generatedClassName;
}
Set<AttributeInfo> getAttributeInfo() {
return attributeInfo;
}
boolean shouldGenerateModel() {
return shouldGenerateModel;
}
Iterable<TypeVariableName> getTypeVariables() {
return typeVariableNames;
}
TypeName getParameterizedGeneratedName() {
return parameterizedClassName;
}
/**
* Get the object type this model is typed with.
*/
TypeName getModelType() {
return boundObjectTypeName;
}
static class ConstructorInfo {
final Set<Modifier> modifiers;
final List<ParameterSpec> params;
final boolean varargs;
ConstructorInfo(Set<Modifier> modifiers, List<ParameterSpec> params, boolean varargs) {
this.modifiers = modifiers;
this.params = params;
this.varargs = varargs;
}
}
static class MethodInfo {
final String name;
final Set<Modifier> modifiers;
final List<ParameterSpec> params;
final boolean varargs;
MethodInfo(String name, Set<Modifier> modifiers, List<ParameterSpec> params,
boolean varargs) {
this.name = name;
this.modifiers = modifiers;
this.params = params;
this.varargs = varargs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodInfo that = (MethodInfo) o;
if (varargs != that.varargs) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (modifiers != null ? !modifiers.equals(that.modifiers) : that.modifiers != null) {
return false;
}
return params != null ? params.equals(that.params) : that.params == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (modifiers != null ? modifiers.hashCode() : 0);
result = 31 * result + (params != null ? params.hashCode() : 0);
result = 31 * result + (varargs ? 1 : 0);
return result;
}
}
@Override
public String toString() {
return "GeneratedModelInfo{"
+ "attributeInfo=" + attributeInfo
+ ", superClassName=" + superClassName
+ '}';
}
}