package com.sora.util.akatsuki;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import javax.lang.model.element.Modifier;
import org.junit.Test;
import android.os.Bundle;
import com.sora.util.akatsuki.RetainedStateTestEnvironment.BundleRetainerTester;
import com.sora.util.akatsuki.TransformationTemplate.Execution;
import com.sora.util.akatsuki.TransformationTemplate.StatementTemplate;
import com.sora.util.akatsuki.TypeConstraint.Bound;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.FieldSpec.Builder;
public class TransformationTemplateIntegrationTest extends RetainedStateIntegrationTestBase {
@Test
public void testClassConstraint() {
testTransformationTemplate(Bound.EXACTLY, StringObject.class, StringObject.class);
}
@Test(expected = RuntimeException.class)
public void testClassConstraintOnInterfaces() {
// you can't retain an interface, it has no fields
testTransformationTemplate(Bound.EXACTLY, StringObject.class, RandomInterface.class);
}
@Test(expected = RuntimeException.class)
public void testInvalidClassConstraint() {
// should not match anything and fail to compile
testTransformationTemplate(Bound.EXACTLY, StringObject.class, BaseStringObject.class);
}
@Test
public void testSubClassConstraint() {
testTransformationTemplate(Bound.EXTENDS, StringObject.class, InheritedStringObject.class);
}
@Test
public void testSubClassConstraintOnInterfaces() {
testTransformationTemplate(Bound.EXTENDS, StringObject.class, RandomInterface.class);
}
@Test
public void testSuperClassConstraint() {
testTransformationTemplate(Bound.SUPER, StringObject.class, BaseStringObject.class);
}
@Test
public void testSuperClassConstraintOnInterfaces() {
testTransformationTemplate(Bound.SUPER, StringObject.class, BaseRandomInterface.class);
}
@Test
public void testAnnotationConstraint() {
testTransformationTemplate(Bound.EXACTLY, StringObject.class, RandomAnnotation.class);
}
@Test
public void testInheritedAnnotationConstraint() {
testTransformationTemplate(Bound.EXTENDS, InheritedStringObject.class,
RandomAnnotation.class);
testTransformationTemplate(Bound.SUPER, InheritedStringObject.class,
RandomAnnotation.class);
}
@Test
public void testMixedConstraint() {
testTransformationTemplate(Bound.EXACTLY, StringObject.class, StringObject.class,
RandomAnnotation.class);
}
// leave unimplemented for now
// @Test
// public void testCompoundType() {
// final TestSource testClass = new TestSource(TEST_PACKAGE,
// generateClassName(),
// Modifier.PUBLIC).fields(new RetainedTestField(List.class, "a",
// "new ArrayList<>()", StringObject.class)
// .createFieldSpec());
// testClass.builderTransformer(
// (builder, source) -> builder.addAnnotation(createTransformationTemplate(
// Bound.EXACTLY, StringObject.class, RandomAnnotation.class)));
//
// final TestEnvironment environment = new TestEnvironment(this, testClass);
//
// environment.invokeSaveAndRestore();
// environment.testSaveRestoreInvocation(n -> true,
// TestEnvironment.CLASS_EQ,
// Collections.singleton(new RetainedTestField(List.class, "a",
// StringObject.class)));
//
// }
@Test
public void testTypeConverter() {
testTypeConverter(false);
}
@Test
public void testRegisteredTypeConverter() {
testTypeConverter(true);
}
protected AnnotationSpec createTransformationTemplate(Bound bound, Class<?> staticClass,
Class<?>... constraints) {
final String objectFqcn = staticClass.getCanonicalName();
AnnotationSpec save = AnnotationSpec
.builder(StatementTemplate.class).addMember("value",
"\"{{bundle}}.putString({{keyName}}, $L.wrap({{fieldName}}))\"", objectFqcn)
.build();
AnnotationSpec restore = AnnotationSpec.builder(StatementTemplate.class)
.addMember("type", "$T.$L", StatementTemplate.Type.class,
StatementTemplate.Type.ASSIGNMENT)
.addMember("value", "\"$L.unwrap({{bundle}}.getString({{keyName}}))\"", objectFqcn)
.addMember("variable", "\"{{fieldName}}\"").build();
final AnnotationSpec.Builder annotationSpec = AnnotationSpec
.builder(TransformationTemplate.class).addMember("save", "$L", save)
.addMember("restore", "$L", restore)
.addMember("execution", "$T.$L", Execution.class, Execution.BEFORE);
for (Class<?> constraint : constraints) {
final AnnotationSpec constraintsSpec = AnnotationSpec.builder(TypeConstraint.class)
.addMember("type", "$T.class", constraint)
.addMember("bound", "$T.$L", Bound.class, bound).build();
AnnotationSpec.Builder filterSpec = AnnotationSpec.builder(TypeFilter.class);
filterSpec.addMember("type", "$L", constraintsSpec);
annotationSpec.addMember("filters", "$L", filterSpec.build());
}
return annotationSpec.build();
}
protected void testTransformationTemplate(Bound bound, Class<?> staticClass,
Class<?>... constraints) {
final String objectFqcn = staticClass.getCanonicalName();
final TestSource testClass = new TestSource(TEST_PACKAGE, generateClassName(),
Modifier.PUBLIC)
.appendFields(new RetainedTestField(StringObject.class, "a", "new " +
objectFqcn + "(\"A\")").createFieldSpec());
testClass.appendTransformation((builder, source) -> builder
.addAnnotation(createTransformationTemplate(bound, staticClass, constraints)));
final RetainedStateTestEnvironment environment = new RetainedStateTestEnvironment(this,
testClass);
environment.tester().invokeSaveAndRestore();
environment.tester().testSaveRestoreInvocation(n -> true, BundleRetainerTester.CLASS_EQ,
Collections.singleton(new RetainedTestField(String.class, "a")), f -> 1);
}
protected void testTypeConverter(boolean registered) {
final TestField retainedField = new TestField(StringObject.class, "a",
"new " + StringObject.class.getCanonicalName() + "(\"A\")");
final Builder fieldBuilder = retainedField.fieldSpecBuilder();
final ArrayList<TestSource> sources = new ArrayList<>();
if (registered) {
fieldBuilder.addAnnotation(Retained.class);
final AnnotationSpec constraintSpec = AnnotationSpec.builder(TypeConstraint.class)
.addMember("type", "$T.class", StringObject.class).build();
final AnnotationSpec.Builder filterSpec = AnnotationSpec.builder(TypeFilter.class)
.addMember("type", "$L", constraintSpec);
final AnnotationSpec converterSpec = AnnotationSpec.builder(DeclaredConverter.class)
.addMember("value", "$L", filterSpec.build()).build();
final String converterName = generateClassName();
TestSource converter = new TestSource(TEST_PACKAGE, converterName, Modifier.PUBLIC)
.appendTransformation(
(builder, source) -> builder.superclass(StringObjectTypeConverter.class)
.addAnnotation(converterSpec));
sources.add(converter);
} else {
fieldBuilder.addAnnotation(AnnotationSpec.builder(Retained.class).build());
fieldBuilder.addAnnotation(AnnotationSpec.builder(With.class)
.addMember("value", "$T.class", StringObjectTypeConverter.class).build());
}
final TestSource testClass = new TestSource(TEST_PACKAGE, generateClassName(),
Modifier.PUBLIC).appendFields(fieldBuilder.build());
// out test class always goes in front
sources.add(0, testClass);
final RetainedStateTestEnvironment environment = new RetainedStateTestEnvironment(this,
sources);
environment.tester().invokeSaveAndRestore();
}
public static class StringObjectTypeConverter implements TypeConverter<StringObject> {
@Override
public void save(Bundle bundle, StringObject stringObject, String key) {
bundle.putString(key, stringObject.actualString);
}
@Override
public StringObject restore(Bundle bundle, StringObject stringObject, String key) {
stringObject.actualString = bundle.getString(key);
return stringObject;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RandomAnnotation {
}
public static abstract class BaseStringObject {
}
public interface BaseRandomInterface {
}
public interface RandomInterface extends BaseRandomInterface {
}
@RandomAnnotation
public static class StringObject extends BaseStringObject implements RandomInterface {
private String actualString;
public StringObject(String actualString) {
this.actualString = actualString;
}
public static String wrap(StringObject object) {
return object == null ? null : object.actualString;
}
public static StringObject unwrap(String actualString) {
return new StringObject(actualString);
}
}
public static class InheritedStringObject extends StringObject {
public InheritedStringObject(String actualString) {
super(actualString);
}
}
}