package pocketknife.internal.codegen.binding;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import pocketknife.internal.BundleBinding;
import pocketknife.internal.codegen.Access;
import pocketknife.internal.codegen.BaseGenerator;
import pocketknife.internal.codegen.BundleFieldBinding;
import pocketknife.internal.codegen.TypeUtil;
import javax.lang.model.type.TypeMirror;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static javax.lang.model.element.Modifier.PUBLIC;
import static pocketknife.internal.codegen.BundleFieldBinding.AnnotationType.ARGUMENT;
import static pocketknife.internal.codegen.BundleFieldBinding.AnnotationType.SAVE_STATE;
public final class BundleBindingAdapterGenerator extends BaseGenerator {
public static final String SAVE_METHOD = "saveInstanceState";
public static final String RESTORE_METHOD = "restoreInstanceState";
public static final String BIND_ARGUMENTS_METHOD = "bindArguments";
private static final String BUNDLE = "bundle";
private static final String TARGET = "target";
public static final String SUPER_METHOD_TEMPLATE = "super.$L($N, $N)";
public static final String THROW_NEW_DOLLAR_SIGN_T_PARENTHESES_DOLLAR_SIGN_S_PARENTHESES = "throw new $T($S)";
private final Set<BundleFieldBinding> fields = new LinkedHashSet<BundleFieldBinding>();
private final String classPackage;
private final String className;
private final TypeMirror targetType;
private boolean required = false;
private ClassName parentAdapter;
public BundleBindingAdapterGenerator(String classPackage, String className, TypeMirror targetType, TypeUtil typeUtil) {
super(typeUtil);
this.classPackage = classPackage;
this.className = className;
this.targetType = targetType;
}
public void addField(BundleFieldBinding binding) {
fields.add(binding);
}
public void orRequired(boolean required) {
this.required |= required;
}
public JavaFile generate() throws IOException {
TypeVariableName t = TypeVariableName.get("T");
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
.addTypeVariable(TypeVariableName.get(t.name, ClassName.get(targetType)))
.addModifiers(PUBLIC)
.addJavadoc(getGeneratedComment(BundleBindingAdapterGenerator.class));
if (parentAdapter != null) {
classBuilder.superclass(ParameterizedTypeName.get(parentAdapter, t));
} else {
classBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(BundleBinding.class), t));
}
addSaveStateMethod(classBuilder, t);
addRestoreStateMethod(classBuilder, t);
addBindingArgumentsMethod(classBuilder, t);
return JavaFile.builder(classPackage, classBuilder.build()).build();
}
private void addSaveStateMethod(TypeSpec.Builder classBuilder, TypeVariableName t) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(SAVE_METHOD)
.addModifiers(PUBLIC)
.addParameter(ParameterSpec.builder(t, TARGET).build())
.addParameter(ParameterSpec.builder(ClassName.get(typeUtil.bundleType), BUNDLE).build());
if (parentAdapter != null) {
methodBuilder.addStatement(SUPER_METHOD_TEMPLATE, SAVE_METHOD, TARGET, BUNDLE);
}
for (BundleFieldBinding field : fields) {
if (SAVE_STATE == field.getAnnotationType()) {
Access access = field.getAccess();
boolean method = access.getType() == Access.Type.METHOD;
if (field.getBundleSerializer() == null) {
String stmt;
if (method) {
stmt = "$N.put$L($S, $N.$N())";
} else {
stmt = "$N.put$L($S, $N.$N)";
}
methodBuilder.addStatement(stmt, BUNDLE, field.getBundleType(), field.getKey().getValue(), TARGET, access.getGetter());
} else {
String stmt;
if (method) {
stmt = "new $T().put($N, $N.$N(), $S)";
} else {
stmt = "new $T().put($N, $N.$N, $S)";
}
methodBuilder.addStatement(stmt, field.getBundleSerializer(), BUNDLE, TARGET, access.getGetter(),
field.getKey().getValue());
}
}
}
classBuilder.addMethod(methodBuilder.build());
}
private void addRestoreStateMethod(TypeSpec.Builder classBuilder, TypeVariableName t) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(RESTORE_METHOD)
.addModifiers(PUBLIC)
.addParameter(ParameterSpec.builder(t, TARGET).build())
.addParameter(ParameterSpec.builder(ClassName.get(typeUtil.bundleType), BUNDLE).build());
if (parentAdapter != null) {
methodBuilder.addStatement(SUPER_METHOD_TEMPLATE, RESTORE_METHOD, TARGET, BUNDLE);
}
methodBuilder.beginControlFlow("if ($N != null)", BUNDLE);
for (BundleFieldBinding field : fields) {
if (SAVE_STATE == field.getAnnotationType()) {
addGetStatement(methodBuilder, field);
}
}
methodBuilder.endControlFlow();
classBuilder.addMethod(methodBuilder.build());
}
private void addGetStatement(MethodSpec.Builder methodBuilder, BundleFieldBinding field) {
Access access = field.getAccess();
boolean method = access.getType() == Access.Type.METHOD;
if (field.getBundleSerializer() == null) {
methodBuilder.beginControlFlow("if ($N.containsKey($S))", BUNDLE, field.getKey().getValue());
List<Object> stmtArgs = new ArrayList<Object>();
String stmt;
if (method) {
stmt = "$N.$N(";
} else {
stmt = "$N.$N = ";
}
stmtArgs.add(TARGET);
stmtArgs.add(access.getSetter());
if (field.needsToBeCast()) {
stmt = stmt.concat("($T)");
stmtArgs.add(field.getType());
}
stmt = stmt.concat("$N.get$L($S");
stmtArgs.add(BUNDLE);
stmtArgs.add(field.getBundleType());
stmtArgs.add(field.getKey().getValue());
if (field.canHaveDefault()) {
if (method) {
stmt = stmt.concat(", $N.$N()");
} else {
stmt = stmt.concat(", $N.$N");
}
stmtArgs.add(TARGET);
stmtArgs.add(access.getGetter());
}
stmt = stmt.concat(")");
if (method) {
stmt = stmt.concat(")");
}
methodBuilder.addStatement(stmt, stmtArgs.toArray(new Object[stmtArgs.size()]));
if (field.isRequired()) {
methodBuilder.nextControlFlow("else");
methodBuilder.addStatement(THROW_NEW_DOLLAR_SIGN_T_PARENTHESES_DOLLAR_SIGN_S_PARENTHESES, IllegalStateException.class,
String.format("Required Bundle value with key '%s' was not found for '%s'. "
+ "If this field is not required add '@NotRequired' annotation",
field.getKey().getValue(), field.getName()));
}
methodBuilder.endControlFlow();
} else {
String stmt;
if (method) {
stmt = "$N.$N(new $T().get($N, $N.$N(), $S))";
} else {
stmt = "$N.$N = new $T().get($N, $N.$N, $S)";
}
methodBuilder.addStatement(stmt, TARGET, access.getSetter(), field.getBundleSerializer(), BUNDLE, TARGET,
access.getGetter(), field.getKey().getValue());
}
}
private void addBindingArgumentsMethod(TypeSpec.Builder classBuilder, TypeVariableName t) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(BIND_ARGUMENTS_METHOD)
.addModifiers(PUBLIC)
.addParameter(ParameterSpec.builder(t, TARGET).build())
.addParameter(ParameterSpec.builder(ClassName.get(typeUtil.bundleType), BUNDLE).build());
if (parentAdapter != null) {
methodBuilder.addStatement(SUPER_METHOD_TEMPLATE, BIND_ARGUMENTS_METHOD, TARGET, BUNDLE);
}
methodBuilder.beginControlFlow("if ($N == null)", BUNDLE);
if (required) {
methodBuilder.addStatement(THROW_NEW_DOLLAR_SIGN_T_PARENTHESES_DOLLAR_SIGN_S_PARENTHESES, IllegalStateException.class, "Argument bundle is null");
} else {
methodBuilder.addStatement("$N = new $T()", BUNDLE, ClassName.get(typeUtil.bundleType));
}
methodBuilder.endControlFlow();
for (BundleFieldBinding field : fields) {
if (ARGUMENT == field.getAnnotationType()) {
addGetArgumentStatement(methodBuilder, field);
}
}
classBuilder.addMethod(methodBuilder.build());
}
private void addGetArgumentStatement(MethodSpec.Builder methodBuilder, BundleFieldBinding field) {
Access access = field.getAccess();
boolean method = access.getType() == Access.Type.METHOD;
if (field.getBundleSerializer() == null) {
methodBuilder.beginControlFlow("if ($N.containsKey($S))", BUNDLE, field.getKey().getValue());
List<Object> stmtArgs = new ArrayList<Object>();
String stmt;
if (method) {
stmt = "$N.$N(";
} else {
stmt = "$N.$N = ";
}
stmtArgs.add(TARGET);
stmtArgs.add(access.getSetter());
if (field.needsToBeCast()) {
stmt = stmt.concat("($T)");
stmtArgs.add(field.getType());
}
stmt = stmt.concat("$N.get$L($S");
stmtArgs.add(BUNDLE);
stmtArgs.add(field.getBundleType());
stmtArgs.add(field.getKey().getValue());
if (field.canHaveDefault()) {
if (method) {
stmt = stmt.concat(", $N.$N()");
} else {
stmt = stmt.concat(", $N.$N");
}
stmtArgs.add(TARGET);
stmtArgs.add(access.getGetter());
}
stmt = stmt.concat(")");
if (method) {
stmt = stmt.concat(")");
}
methodBuilder.addStatement(stmt, stmtArgs.toArray(new Object[stmtArgs.size()]));
if (field.isRequired()) {
methodBuilder.nextControlFlow("else");
methodBuilder.addStatement(THROW_NEW_DOLLAR_SIGN_T_PARENTHESES_DOLLAR_SIGN_S_PARENTHESES, IllegalStateException.class,
String.format("Required Bundle value with key '%s' was not found for '%s'. "
+ "If this field is not required add '@NotRequired' annotation",
field.getKey().getValue(), field.getName()));
}
methodBuilder.endControlFlow();
} else {
String stmt;
if (method) {
stmt = "$N.$N(new $T().get($N, $N.$N(), $S))";
} else {
stmt = "$N.$N = new $T().get($N, $N.$N, $S)";
}
methodBuilder.addStatement(stmt, TARGET, access.getSetter(), field.getBundleSerializer(), BUNDLE, TARGET,
access.getGetter(), field.getKey().getValue());
}
}
public void setParentAdapter(String packageName, String className) {
this.parentAdapter = ClassName.get(packageName, className);
}
}