package com.sora.util.akatsuki;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import android.os.Bundle;
import com.sora.util.akatsuki.Internal.ArgBuilder;
import com.sora.util.akatsuki.RetainedStateTestEnvironment.BundleRetainerTester;
public class BuilderTestEnvironment extends BaseTestEnvironment {
public BuilderTestEnvironment(IntegrationTestBase base, List<TestSource> sources) {
super(base, sources);
}
public BuilderTestEnvironment(IntegrationTestBase base, TestSource source,
TestSource... required) {
super(base, source, required);
}
@Override
protected void setupTestEnvironment() throws Exception {
// nah
}
static class SingleBuilderTester {
// these classes form a valid builder
private final Class<?> builderParentClass;
private final Class<?> builderClass;
private final Class<?> retainerClass;
private final BuilderTestEnvironment environment;
private final TestSource source;
private final Method builderClassMethodWithBundle;
protected ArgBuilder<?> builderInstance;
protected BundleRetainer<Object> builderBundleRetainer;
private Bundle mockedBundle;
public SingleBuilderTester(BuilderTestEnvironment environment, TestSource source) {
this.environment = environment;
this.source = source;
// verify builder structure
try {
String builderParentName = source.fqpn() + "."
+ Internal.BUILDER_CLASS_NAME;
builderParentClass = environment.classLoader().loadClass(builderParentName);
String builderClassName = source.className()
+ Internal.BUILDER_CLASS_SUFFIX;
// <package>.<builderName>
builderClass = environment.classLoader()
.loadClass(builderParentName + "$" + builderClassName);
// <package>.<builderName>.<retainerName>
retainerClass = environment.classLoader()
.loadClass(Internal.generateRetainerClassName(builderParentName + "$"
+ builderClassName + "$" + builderClassName));
try {
builderClassMethodWithBundle = builderParentClass.getMethod(source.simpleName(),
Bundle.class);
Method builderClassMethod = builderParentClass.getMethod(source.simpleName());
assertEquals("Different return types from builder method, should not happen",
builderClassMethod.getReturnType(),
builderClassMethodWithBundle.getReturnType());
assertEquals("Builder methods should not have any parameters",
builderClassMethod.getParameterCount(), 0);
assertTrue("Builder methods should be public",
Modifier.isPublic(builderClassMethod.getModifiers()) && Modifier
.isPublic(builderClassMethodWithBundle.getModifiers()));
Class<?> builderMethodReturnType = builderClassMethod.getReturnType();
// builderClassMethod and builderClassMethodWithBundle
// should have same return type at this point
assertEquals("Builder class should equal method return class", builderClass,
builderMethodReturnType);
} catch (NoSuchMethodException e) {
throw new AssertionError("Builder accessing method not found", e);
}
} catch (ClassNotFoundException e) {
throw new AssertionError("Class expected but missing", e);
}
}
public void initializeAndValidate() {
Object builderParentInstance;
try {
builderParentInstance = builderParentClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new AssertionError("Unable to instantiate Builders class for package "
+ source.packageName + " for source class " + source.toString(), e);
}
try {
mockedBundle = mock(Bundle.class);
builderInstance = (ArgBuilder<?>) builderClassMethodWithBundle
.invoke(builderParentInstance, mockedBundle);
initializeRetainer();
} catch (ClassCastException e) {
throw new AssertionError("The returned builder class does not extend ArgBuilder",
e);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new AssertionError("Unable to invoke builder method", e);
}
}
@SuppressWarnings("unchecked")
private void initializeRetainer() {
try {
builderBundleRetainer = (BundleRetainer<Object>) retainerClass.newInstance();
} catch (ClassCastException e) {
throw new AssertionError(
"Retainer class " + retainerClass + " does not implement BundleRetainer",
e);
} catch (InstantiationException | IllegalAccessException e) {
throw new AssertionError(
"Unable to create new instance of retainer class " + retainerClass, e);
}
}
public Bundle mockedBundle() {
return mockedBundle;
}
public BundleRetainerTester retainerTester() {
try {
return new RetainedStateTestEnvironment.BundleRetainerTester(environment,
mock(environment.findClass(source.fqcn())), mockedBundle,
builderBundleRetainer);
} catch (ClassNotFoundException e) {
throw new AssertionError("Unable to find class for " + source, e);
}
}
public void assertMethodCountMatched(int methodCount, Predicate<Method> methodPredicate) {
filterAndCountMethod(methodCount, methodPredicate, methods -> {
throw new AssertionError(
"Predicate matched more or less methods than expected, required "
+ methodCount + " but matched " + methods);
});
}
public void filterAndCountMethod(int methodCount, Predicate<Method> methodPredicate,
Consumer<Set<Method>> action) {
Set<Method> methods = Arrays.stream(builderClass.getMethods()).filter(methodPredicate)
.collect(Collectors.toSet());
System.out.println("Matching " + methods + " for " + methodCount);
if (methods.size() != methodCount) {
action.accept(methods);
}
}
}
public SingleBuilderTester assertBuilderGeneratedAndValid(Predicate<TestSource> predicate) {
List<TestSource> sources = this.sources.stream().filter(predicate)
.collect(Collectors.toList());
if (sources.size() != 1)
throw new AssertionError(
"none or more than one result matched with the given predicate, found:"
+ sources);
SingleBuilderTester tester = new SingleBuilderTester(this, sources.get(0));
tester.initializeAndValidate();
return tester;
}
public List<SingleBuilderTester> assertAllBuildersGeneratedAndValid() {
return sources.stream().map(s -> new SingleBuilderTester(this, s))
.peek(SingleBuilderTester::initializeAndValidate).collect(Collectors.toList());
}
}