package xapi.dev.ui;
import com.github.javaparser.ASTHelper;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.plugin.NodeTransformer;
import com.github.javaparser.ast.visitor.ConcreteModifierVisitor;
import com.github.javaparser.ast.visitor.ModifierVisitorAdapter;
import xapi.annotation.inject.InstanceDefault;
import xapi.dev.processor.AnnotationTools;
import xapi.dev.source.SourceBuilder;
import xapi.dev.ui.ContainerMetadata.MetadataRoot;
import xapi.dev.ui.InterestingNodeFinder.InterestingNodeResults;
import xapi.fu.In2Out1;
import xapi.fu.Out1;
import xapi.io.X_IO;
import xapi.log.X_Log;
import xapi.source.X_Source;
import xapi.ui.api.Ui;
import xapi.ui.api.UiPhase.PhaseBinding;
import xapi.ui.api.UiPhase.PhaseImplementation;
import xapi.ui.api.UiPhase.PhaseIntegration;
import xapi.ui.api.UiPhase.PhasePreprocess;
import xapi.ui.api.UiPhase.PhaseSupertype;
import xapi.util.X_Debug;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.OutputStream;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
* Created by James X. Nelson (james @wetheinter.net) on 6/26/16.
*/
@InstanceDefault(implFor = UiGeneratorService.class)
public class UiGeneratorServiceDefault extends AbstractUiGeneratorService {
private AnnotationTools service;
private In2Out1<UiContainerExpr, ContainerMetadata, UiComponentGenerator> componentFactory;
private In2Out1<UiAttrExpr, UiComponentGenerator, UiFeatureGenerator> featureFactory;
public UiGeneratorServiceDefault() {
resetFactories();
}
private void resetFactories() {
componentFactory = super::getComponentGenerator;
featureFactory = super::getFeatureGenerator;
}
@Override
public ComponentBuffer initialize(
AnnotationTools service, TypeElement type, Ui ui, UiContainerExpr container
) {
this.service = service;
final String pkgName = service.getPackageName(type);
final String simpleName = X_Source.enclosedNameFlattened(pkgName, type.getQualifiedName().toString());
final MetadataRoot root = new MetadataRoot();
final ContainerMetadata metadata = createMetadata(root, container);
final ComponentBuffer component = new ComponentBuffer(metadata);
component.setElement(type);
metadata.setControllerType(pkgName, simpleName);
String generatedName = calculateGeneratedName(pkgName, simpleName, container);
final SourceBuilder<ContainerMetadata> b = new SourceBuilder<>();
b.setClassDefinition("public class " + generatedName, false);
b.setPackage(pkgName);
metadata.setSourceBuilder(b);
return component;
}
@Override
public ComponentBuffer runPhase(String id, ComponentBuffer component) {
component = super.runPhase(id, component);
switch (id) {
case PhasePreprocess.PHASE_PREPROCESS:
return preprocessComponent(component);
case PhaseSupertype.PHASE_SUPERTYPE:
return createSupertype(component);
case PhaseImplementation.PHASE_IMPLEMENTATION:
return createImplementation(component);
case PhaseIntegration.PHASE_INTEGRATION:
return peekIntegration(component);
case PhaseBinding.PHASE_BINDING:
return runBinding(component);
default:
return runCustomPhase(id, component);
}
}
protected ComponentBuffer preprocessComponent(ComponentBuffer component) {
// Find all refs, datanodes and other interesting bits of data.
final ContainerMetadata metadata = component.getRoot();
UiContainerExpr container = metadata.getUi();
// replace all <import /> tags with imported resources
container = resolveImports(service.getElements(), service.getFileManager(), component.getElement(), container);
if (container.getAttribute("ref") == null) {
container.addAttribute(false, new UiAttrExpr("ref", new StringLiteralExpr("root")));
}
metadata.setContainer(container);
// find and resolve all nodes with ref attributes
final InterestingNodeResults interestingNodes = new InterestingNodeFinder().findInterestingNodes(container);
component.setInterestingNodes(interestingNodes);
return component;
}
@Override
public UiComponentGenerator getComponentGenerator(UiContainerExpr container, ContainerMetadata metadata) {
return componentFactory.io(container, metadata);
}
@Override
public UiFeatureGenerator getFeatureGenerator(UiAttrExpr container, UiComponentGenerator componentGenerator) {
return featureFactory.io(container, componentGenerator);
}
protected ComponentBuffer createSupertype(ComponentBuffer component) {
if (component.hasDataNodes()) {
generateDataAccessors(component);
}
if (component.hasCssOrClassname()) {
generateCssPrimitives(component);
}
if (component.hasTemplateReferences()) {
rewriteTemplateReferences(component);
}
SourceBuilder<ContainerMetadata> binder = component.getBinder();
// TODO add @Generated tag with all resources we are dependent upon
final SourceBuilder<ContainerMetadata> root = component.getBinder();
onDone.add(()->
saveGeneratedComponent(root)
);
return component;
}
@Override
public String calculateGeneratedName(
String pkgName, String className, UiContainerExpr expr
) {
return "Super" + super.calculateGeneratedName(pkgName, className, expr);
}
private void saveGeneratedComponent(SourceBuilder<?> binder) {
final String src = binder.toSource();
try {
// TODO: add source element types of anything we loaded during compilation
final JavaFileObject out = service.outputJava(binder.getQualifiedName());
try (OutputStream o = out.openOutputStream()) {
X_IO.drain(o, X_IO.toStreamUtf8(src));
}
X_Log.info(getClass(), "Generating ui into ", out.toUri(), "\n", src);
} catch (IOException e) {
throw X_Debug.rethrow(e);
}
}
protected void rewriteTemplateReferences(ComponentBuffer component) {
final InterestingNodeResults interestingNodes = component.getInterestingNodes();
Set<UiContainerExpr> templateParents = interestingNodes.getTemplateNameParents();
componentFactory = containerFilter(templateParents);
featureFactory = (feature, gen) -> {
if (templateParents.contains(ASTHelper.getContainerParent(feature))) {
rewriteDataReferences(component, feature, gen);
return new UiFeatureGenerator();
} else {
return null;
}
};
final ContainerMetadata metadata = component.getRoot();
UiGeneratorVisitor visitor = createVisitor(component.getRoot());
visitor.visit(metadata.getUi(), this);
resetFactories();
}
protected void rewriteDataReferences(ComponentBuffer component, UiAttrExpr n, UiComponentGenerator gen) {
final ContainerMetadata me = gen.getMetadata();
final Expression expr = n.getExpression();
Map<Node, Out1<Node>> replacements = new IdentityHashMap<>();
final ComponentMetadataQuery query = new ComponentMetadataQuery();
query.setVisitAttributeContainers(false);
query.setVisitChildContainers(false);
expr.accept(
new ComponentMetadataFinder(),
query
.addNameListener((graph, name) -> {
String replacement;
switch (name.getName()) {
case "$root":
replacement = me.getRootReference();
break;
default:
if (query.isTemplateName(name.getName())) {
replacement = query.normalizeTemplateName(name.getName());
} else {
replacement = null;
}
}
String ref = replacement;
name.setName(ref);
name.getParentNode().getParentNode().accept(new ModifierVisitorAdapter<Object>() {
@Override
public Node visit(
FieldAccessExpr n, Object arg
) {
String var = n.getField();
NodeTransformer newNode = me.findReplacement(ref, var);
if (newNode != null) {
// If this node is the qualifier on a field access,
// then we may want to perform additional transformations...
if (n.getParentNode() instanceof FieldAccessExpr) {
// A field access may be shorthand notation for a map access...
// The data field was a qualifier of a field access...
FieldAccessExpr parent = (FieldAccessExpr) n.getParentNode();
if (parent.getParentNode() instanceof UnaryExpr) {
// A + - ++ -- ! or ~ expression. We will replace this with a compute call
// if one is available...
UnaryExpr toReplace = (UnaryExpr) parent.getParentNode();
// ++ and -- must be handled specially, as they perform
// a read and a write
parent.setScope((Expression) newNode.getNode());
replacements.put(toReplace, () -> newNode.transformUnary(n, toReplace));
} else if (parent.getParentNode() instanceof BinaryExpr) {
// A && || = > < >= <= etc binary expression;
// These are safe to replace as simple get operations,
// as they do not perform assignment
BinaryExpr toReplace = (BinaryExpr) parent.getParentNode();
replacements.put(toReplace, () -> newNode.transformBinary(n, toReplace));
} else if (parent.getParentNode() instanceof AssignExpr) {
AssignExpr toReplace = (AssignExpr) parent.getParentNode();
// A plain = assignment will be transformed into a write,
// while all other assignment, += -= etc will need to read and write
final Node result = newNode.transformAssignExpr(toReplace);
replacements.put(
toReplace,
() -> newNode.transformAssignExpr(toReplace)
);
}
} else if (n.getParentNode() instanceof ArrayAccessExpr) {
// An array access may be shorthand notation for a list access
ArrayAccessExpr toReplace = (ArrayAccessExpr) n.getParentNode();
final Node result = newNode.transformArrayAccess(toReplace);
replacements.put(toReplace, () -> newNode.transformArrayAccess(toReplace));
}
return newNode.getNode();
}
return super.visit(n, arg);
}
}, null);
})
);
if (!replacements.isEmpty()) {
ConcreteModifierVisitor.replaceResolved(replacements);
}
}
protected void generateCssPrimitives(ComponentBuffer component) {
final InterestingNodeResults interestingNodes = component.getInterestingNodes();
Set<UiContainerExpr> cssParents = interestingNodes.getCssParents();
componentFactory = containerFilter(cssParents);
featureFactory = (feature, gen) -> {
switch (feature.getNameString().toLowerCase()) {
case "css":
case "style":
case "class":
return new CssFeatureGenerator();
}
return null;
};
final ContainerMetadata metadata = component.getRoot();
UiGeneratorVisitor visitor = createVisitor(component.getRoot());
visitor.visit(metadata.getUi(), this);
resetFactories();
}
protected void generateDataAccessors(ComponentBuffer component) {
final InterestingNodeResults interestingNodes = component.getInterestingNodes();
Set<UiContainerExpr> dataParents = interestingNodes.getDataParents();
componentFactory = containerFilter(dataParents);
featureFactory = (feature, gen) -> {
if (feature.getNameString().equalsIgnoreCase("data")) {
return new DataFeatureGenerator();
} else if (dataParents.contains(ASTHelper.getContainerParent(feature))) {
// TODO map features which contain nested UiContainExpr via InterestingNodeFinder
return new UiFeatureGenerator();
} else {
return null;
}
};
final ContainerMetadata metadata = component.getRoot();
UiGeneratorVisitor visitor = createVisitor(component.getRoot());
visitor.visit(metadata.getUi(), this);
resetFactories();
}
protected ComponentBuffer createImplementation(ComponentBuffer component) {
// Generate a boilerplate interface that takes generics for all renderable nodes.
for (UiImplementationGenerator service : getImplementations()) {
final ContainerMetadata metadata = component.getImplementation(service.getClass());
service.setGenerator(this);
final ContainerMetadata result = service.generateComponent(metadata, component);
saveGeneratedComponent(result.getSourceBuilder());
}
return component;
}
protected Iterable<UiImplementationGenerator> getImplementations() {
return ServiceLoader.load(UiImplementationGenerator.class);
}
protected ComponentBuffer peekIntegration(ComponentBuffer component) {
return component;
}
protected ComponentBuffer runBinding(ComponentBuffer component) {
return component;
}
protected ComponentBuffer runCustomPhase(String id, ComponentBuffer component) {
return component;
}
}