package com.sora.util.akatsuki;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import javax.tools.JavaFileObject;
import com.google.testing.compile.JavaFileObjects;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeSpec.Builder;
public final class TestSource {
public final String packageName;
private final String className;
public final List<FieldSpec> specs = new ArrayList<>();
public final List<TestSource> innerClasses = new ArrayList<>();
public final Modifier[] modifiers;
private TestSource superClass;
private TestSource enclosingClass;
private final List<BiFunction<Builder, TestSource, Builder>> builderTransformers = new ArrayList<>();
/**
* Creates a top level class
*/
public TestSource(String packageName, String className, Modifier... modifiers) {
this.packageName = packageName;
this.className = className;
this.modifiers = modifiers;
}
/**
* Creates a package-less class to be used as an static inner class
*/
public TestSource(String className, Modifier... modifiers) {
this.packageName = null;
this.className = className;
this.modifiers = modifiers;
}
public TestSource appendFields(FieldSpec... specs) {
this.specs.addAll(Arrays.asList(specs));
return this;
}
public TestSource appendFields(Collection<FieldSpec> specs) {
this.specs.addAll(specs);
return this;
}
public TestSource appendTestFields(Collection<TestField> fields) {
return appendFields(
fields.stream().map(TestField::createFieldSpec).toArray(FieldSpec[]::new));
}
public TestSource appendTestFields(TestField... fields) {
return appendFields(
Arrays.stream(fields).map(TestField::createFieldSpec).toArray(FieldSpec[]::new));
}
public TestSource innerClasses(boolean setStatic, TestSource... sources) {
return this.innerClasses(setStatic, Arrays.asList(sources));
}
public TestSource innerClasses(boolean setStatic, Collection<TestSource> sources) {
for (TestSource source : sources) {
if (source.packageName != null)
throw new IllegalArgumentException("inner class " + source
+ " contains a package name, it should be a top level class");
source.enclosingClass = this;
if (setStatic)
source.appendTransformation(
(builder, testSource) -> builder.addModifiers(Modifier.STATIC));
}
this.innerClasses.addAll(sources);
return this;
}
public TestSource superClass(TestSource source) {
source.checkInner();
this.superClass = source;
return this;
}
private Builder specBuilder() {
Builder builder = TypeSpec.classBuilder(className).addModifiers(modifiers).addFields(specs)
.addTypes(innerClasses.stream().map(s -> s.specBuilder().build())
.collect(Collectors.toList()));
if (superClass != null)
builder.superclass(ClassName.get(superClass.packageName, superClass.className));
for (BiFunction<Builder, TestSource, Builder> function : builderTransformers) {
builder = function.apply(builder, this);
}
return builder;
}
public TestSource appendTransformation(BiFunction<Builder, TestSource, Builder> function) {
this.builderTransformers.add(function);
return this;
}
public String generateSource() {
checkInner();
JavaFile javaFile = JavaFile.builder(packageName, specBuilder().build()).build();
return javaFile.toString();
}
public JavaFileObject generateFileObject(BiFunction<String, String, JavaFileObject> supplier) {
checkInner();
return supplier.apply(fqcn(), generateSource());
}
public JavaFileObject generateFileObject() {
checkInner();
return generateFileObject(JavaFileObjects::forSourceString);
}
private void checkInner() {
if (packageName == null)
throw new IllegalArgumentException(
"cannot be called on an inner class : " + toString());
}
public String fqcn() {
return (enclosingClass != null) ? (enclosingClass.fqcn() + "$" + className)
: (packageName + "" + "." + className);
}
public String fqpn() {
return enclosingClass != null ? enclosingClass.fqpn() : packageName;
}
@Override
public String toString() {
return "TestSource{" + "packageName='" + packageName + '\'' + ", className='" + className
+ '\'' + ", specs=" + specs + ", innerClasses=" + innerClasses + ", modifiers="
+ Arrays.toString(modifiers) + ", superClass=" + superClass + '}';
}
public String className() {
return enclosingClass != null ? (enclosingClass.className + "$" + className) : className;
}
public String simpleName(){
return className;
}
public boolean toplevel(){
return packageName != null;
}
}