package xapi.dev.ui;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.expr.UiContainerExpr;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.fu.In1Out1;
import xapi.inject.X_Inject;
import xapi.javac.dev.api.JavacService;
import xapi.log.X_Log;
import xapi.ui.api.PhaseMap;
import xapi.ui.api.PhaseMap.PhaseNode;
import xapi.ui.api.Ui;
import xapi.ui.api.UiElement;
import xapi.ui.api.UiPhase;
import xapi.util.X_Debug;
import xapi.util.X_String;
import static xapi.source.X_Source.removePackage;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Created by james on 6/6/16.
*/
@SupportedAnnotationTypes({"xapi.ui.api.*"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UiAnnotationProcessor extends AbstractProcessor {
private JavacService service;
private PhaseMap<String> phaseMap;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
service = JavacService.instanceFor(processingEnv);
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
final Types typeOracle = service.getTypes();
final Elements elements = service.getElements();
final In1Out1<Class<?>, DeclaredType> lookup = c->{
final TypeElement cls = elements.getTypeElement(c.getCanonicalName());
return typeOracle.getDeclaredType(cls);
};
final DeclaredType uiPhase = lookup.io(UiPhase.class);
Set<TypeElement> types = new LinkedHashSet<>();
Set<UiPhase> phases = new LinkedHashSet<>();
annotations.forEach(anno->{
final Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(anno);
annotated.forEach(ele->{
UiPhase phase = ele.getAnnotation(UiPhase.class);
if (phase != null) {
phases.add(phase);
}
if (ele instanceof TypeElement) {
types.add((TypeElement) ele);
} else {
Element parent = ele;
while (!(parent instanceof TypeElement)) {
parent = parent.getEnclosingElement();
}
types.add((TypeElement) parent);
}
});
});
for (Class<?> phase : UiPhase.CORE_PHASES) {
phases.add(phase.getAnnotation(UiPhase.class));
}
// compute phases to run
phaseMap = PhaseMap.toMap(phases, UiPhase::id, UiPhase::priority, UiPhase::prerequisite, UiPhase::block);
types.forEach(this::generateControllers);
}
return true;
}
private void generateControllers(TypeElement element) {
UiAnnotatedElements elements = findAnnotations(element);
// Now that we have our type / method @Uis, lets create some controllers for them.
assert elements.hasAnyAnnotations() : "Type element " + element + " has an annotation type in " +
"xapi.ui.api package that was not handled by UiAnnotationProcessor...";
boolean needsBinder = elements.hasBoundAnnotations();
if (elements.hasUiAnnotations()) {
// Anything with @Ui annotations is producing UIs
if(generateUiFactory(element, elements)) {
needsBinder = true;
}
}
if (needsBinder) {
// Anything with @UiField annotations is being bound to existing UIs
generateUiBinder(element, elements);
}
}
private boolean generateUiFactory(TypeElement element, UiAnnotatedElements elements) {
String simpleName = element.getSimpleName().toString();
String fqcn = element.getQualifiedName().toString();
SourceBuilder builder = new SourceBuilder("public class " + simpleName + "UiFactory");
final ClassBuffer out = builder.getClassBuffer();
builder.setPackage(fqcn.replace("." + simpleName, ""));
String ele = builder.addImport(UiElement.class);
if (!elements.uiTypes.isEmpty()) {
X_Log.info(getClass(), "Examining ui types for ", element, elements);
elements.uiTypes.forEach((type, ui)->{
final String typeName = type.getQualifiedName().toString();
final String pkgName = builder.getPackage();
String enclosedName = removePackage(pkgName, typeName).replace('.', '_');
String source = X_String.join("\n", ui.value());
UiContainerExpr container;
try {
container= JavaParser.parseUiContainer(source);
} catch (ParseException e) {
throw X_Debug.rethrow(e);
}
MethodBuffer factory = out.createMethod("public " + ele + " create"+enclosedName);
factory.addParameter(simpleName, "from");
generateUiImplementations(builder, factory, type, ui, container);
});
}
if (!elements.uiMethods.isEmpty()) {
}
System.out.println(builder);
return false;
}
private void generateUiImplementations(SourceBuilder<?> out, MethodBuffer factory, TypeElement type, Ui ui, UiContainerExpr container) {
// Generate an abstract, base class that is fully generic.
// Then, for every known type provider on the classpath,
// also generate a platform-specific implementation of that abstract class,
// optionally with a number of configurable interfaces added.
// generate generic base class.
UiGeneratorService generator = getSuperclassGenerator();
ComponentBuffer component = generator.initialize(service, type, ui, container);
// Run each phase, potentially including custom phases injected by third parties
// Note, we need to use forthcoming xapi.properties support to record annotations
// which are annotated with @UiPhase, so a library can be compiled with a record
// of the UiPhase added. Currently, we do not use any custom phases.
for (PhaseNode<String> phase : phaseMap.forEachNode()) {
component = generator.runPhase(phase.getId(), component);
}
generator.finish();
// for (UiGeneratorService uiGeneratorService : services) {
//
// final String pkg = getClass().getPackage().getName();
// final String simpleName = classToEnclosedSourceName(getClass());
// final ContainerMetadata generated = uiGeneratorService.generateComponent(
// pkg, simpleName, container);
//
// final SourceBuilder<?> builder = generated.getSourceBuilder();
// System.out.println(builder);
// String impl = factory.addImport(builder.getQualifiedName());
// factory.returnValue("new " + impl+"().io(from);");
// }
}
protected UiGeneratorService getSuperclassGenerator() {
return X_Inject.instance(UiGeneratorService.class);
}
private void generateUiBinder(TypeElement element, UiAnnotatedElements elements) {
}
private UiAnnotatedElements findAnnotations(TypeElement element) {
final UiAnnotatedElements elements = new UiAnnotatedElements();
elements.maybeAddType(element);
Map<ExecutableElement, Ui> methodUis = new LinkedHashMap<>();
element.getEnclosedElements().forEach(ele -> {
if (ele instanceof TypeElement) {
// we already extracted these..
} else if (ele instanceof ExecutableElement){
ExecutableElement exe = (ExecutableElement) ele;
elements.maybeAddExecutable(exe);
} else if (ele instanceof VariableElement) {
VariableElement var = (VariableElement) ele;
elements.maybeAddVariable(var);
} else {
X_Log.warn(getClass(), "Unsupported enclosed type ", ele.getClass(), ele, "Ignoring...");
}
});
return elements;
}
}