package xapi.dev.elemental;
import com.github.javaparser.ASTHelper;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.plugin.Transformer;
import com.github.javaparser.ast.plugin.UiTranslatorPlugin;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import xapi.fu.Printable;
import xapi.log.X_Log;
import xapi.source.X_Source;
import xapi.util.api.DebugRethrowable;
import java.util.List;
import java.util.Optional;
/**
* @author James X. Nelson (james@wetheinter.net)
* Created on 4/16/16.
*/
public class ElementalTemplatePlugin <Ctx> implements UiTranslatorPlugin, DebugRethrowable {
protected class ElementalUiVisitor extends VoidVisitorAdapter <Ctx> {
private final Expression expr;
private Printable printer;
private boolean hasRoot;
private boolean inCssBlock;
public ElementalUiVisitor(UiExpr expr) {
if (expr instanceof TemplateLiteralExpr) {
String src = ((TemplateLiteralExpr)expr).getValueWithoutTicks();
try {
this.expr = JavaParser.parseExpression(src);
} catch (ParseException e) {
throw rethrow(e);
}
} else {
this.expr = expr;
}
}
@Override
public void visit(ClassOrInterfaceDeclaration n, Ctx arg) {
final Optional<AnnotationExpr> uiAnno = n.getAnnotations().stream()
.filter(anno -> "Ui".equals(anno.getName().getSimpleName()))
.findFirst();
if (uiAnno.isPresent()) {
final AnnotationExpr a = uiAnno.get();
}
super.visit(n, arg);
}
@Override
public void visit(UiContainerExpr n, Ctx arg) {
boolean isRoot = !hasRoot;
if (isRoot) {
hasRoot = true;
printer.print("new PotentialElement(\"");
} else {
printer.indent();
printer.println();
printer.println();
printer.print(".createChild(\"");
}
printer.print(n.getName());
printer.print("\")");
super.visit(n, arg);
printer.println();
if (isRoot) {
printer.print(".build()");
} else {
printer.println(".finishChild()");
printer.outdent();
}
}
@Override
public void visit(CssBlockExpr n, Ctx arg) {
inCssBlock = true;
if (n.getContainers().size() == 1 && n.getContainers().get(0).getSelectors().isEmpty()) {
// This is an inline .{ rule: val; } which does not have any selectors.
// In this case, just print the rules...
printer.print("\"");
n.getContainers().get(0).getRules().forEach(rule->rule.accept(this, arg));
printer.print("\"");
} else {
// A more complex block container which has selectors and rules...
// will need to create css files and inject them
printer.print("null");
super.visit(n, arg);
}
inCssBlock = false;
}
@Override
public void visit(CssContainerExpr n, Ctx arg) {
if (!inCssBlock && n.isSingleClassSelector()) {
// This is a single class selector outside of a css block;
// in this case, we want to print the style externally,
// then print the single classname as a string.
printer.print("\"")
.print(n.getSelectors().get(0).getParts().get(0).substring(1))
.print("\"");
}
// Any other css container should be printed as a css file,
// possibly with a factory that can call any expressions that
// happen to be included in said css.
printer.println(")")
.print(".addCss(")
.indent()
.println()
.print("\"");
final List<CssSelectorExpr> selectors = n.getSelectors();
for (int i = 0; i < selectors.size(); i++) {
selectors.get(i).accept(this, arg);
if (i < selectors.size() -1) {
printer.println(", \"")
.print("\"");
}
}
printer.println(" {\" +");
printer.indent();
final List<CssRuleExpr> rules = n.getRules();
for (int i = 0; i < rules.size(); i++) {
printer.print("\"");
rules.get(i).accept(this, arg);
if (i == rules.size() - 1) {
printer.outdent();
}
printer.println("\" +");
}
printer.println("\"}\"");
printer.outdent();
}
@Override
public void visit(CssSelectorExpr n, Ctx arg) {
final List<String> parts = n.getParts();
for (int i = 0; i < parts.size(); i++) {
String part = parts.get(i);
printer.print(part);
if (i < parts.size() - 1) {
String next = parts.get(i + 1);
if (!next.startsWith(":") || next.startsWith("::selection")) {
printer.print(" ");
}
}
}
}
@Override
public void visit(CssRuleExpr n, Ctx arg) {
// TODO: Handle parenthetical key expressions correctly
String key = ASTHelper.extractStringValue(n.getKey());
printer.print(key).print(" : ");
n.getValue().accept(this, arg);
printer.print(";");
}
@Override
public void visit(CssValueExpr n, Ctx arg) {
n.getValue().accept(this, arg);
if (n.getUnit() != null) {
printer.print(n.getUnit());
}
}
@Override
public void visit(IntegerLiteralExpr n, Ctx arg) {
printer.print(n.getValue());
}
@Override
public void visit(StringLiteralExpr n, Ctx arg) {
printer.print(n.getValue());
}
@Override
public void visit(DoubleLiteralExpr n, Ctx arg) {
printer.print(n.getValue());
}
@Override
public void visit(LongLiteralExpr n, Ctx arg) {
printer.print(n.getValue());
}
@Override
public void visit(CharLiteralExpr n, Ctx arg) {
printer.print(n.getValue());
}
@Override
public void visit(UiAttrExpr n, Ctx arg) {
printer.indent();
printer.println();
printer.print(".set(\"");
printer.print(n.getName().getName());
printer.print("\", ");
final Expression value = n.getExpression();
boolean keepTrying = true;
if (value instanceof LiteralExpr) {
keepTrying = false;
if (value instanceof CssExpr || value instanceof UiContainerExpr || value instanceof JsonExpr) {
value.accept(this, arg);
} else {
String val;
try {
val = ASTHelper.extractStringValue(value);
if (value.getClass() == StringLiteralExpr.class ||
value instanceof TemplateLiteralExpr ||
value instanceof UiExpr) {
val = "\"" + X_Source.escape(val) + "\"";
}
printer.print(val);
} catch (Exception ignored) {
keepTrying = true;
X_Log.debug(getClass(), "ignored exception: ", ignored);
}
}
}
if (keepTrying) {
if (value instanceof NameExpr){
String name = value.toSource();
if (name.startsWith("$")) {
// A $dollar reference!
// We'll want to emit a magic method to handle type coercions in compiler...
printer.print("xapi.fu.X_Fu.coerce(" + name.substring(1) +")");
}
} else {
value.accept(this, arg);
}
}
printer.print(")");
printer.outdent();
// super.visit(n, arg); // leave this disabled until nesting is considered
}
@Override
public void visit(UiBodyExpr n, Ctx arg) {
n.getChildren().forEach(uiExpr -> {
if (uiExpr instanceof TemplateLiteralExpr) {
final String src = ((TemplateLiteralExpr)uiExpr).getValueWithoutTicks();
if (src.chars().allMatch(Character::isWhitespace)) {
// TODO make ignoring whitespace optional...
// probably by enforcing a supertype on the Ctx argument...
return;
}
printer.print(".append(\"");
printer.print(X_Source.escape(src));
printer.print("\")");
} else {
uiExpr.accept(this, arg);
}
});
}
public void printTo(Printable printer, Ctx arg) {
this.printer = printer;
expr.accept(this, arg);
}
}
@Override
public String transformUi(Printable printer, UiExpr ui) {
ElementalUiVisitor visitor = new ElementalUiVisitor(ui);
visitor.printTo(printer, getArgument());
return Transformer.DO_NOT_PRINT;
}
protected Ctx getArgument() {
return null;
}
}