package xapi.dev.ui;
import com.github.javaparser.ASTHelper;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.UiAttrExpr;
import com.github.javaparser.ast.expr.UiContainerExpr;
import com.github.javaparser.ast.plugin.NodeTransformer;
import xapi.collect.X_Collect;
import xapi.collect.api.Fifo;
import xapi.collect.api.StringTo;
import xapi.collect.impl.SimpleLinkedList;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.NameGen;
import xapi.dev.source.SourceBuilder;
import xapi.dev.source.SourceTransform;
import xapi.fu.In1Out1;
import xapi.fu.Lazy;
import xapi.source.X_Source;
import xapi.ui.service.UiService;
import static xapi.fu.Lazy.deferred1;
import java.util.IdentityHashMap;
import java.util.Optional;
/**
* @author James X. Nelson (james@wetheinter.net)
* Created on 5/1/16.
*/
public class ContainerMetadata {
public static class MetadataRoot {
private String rootReference;
private final StringTo<Integer> nameCounts;
private final StringTo<StringTo<NodeTransformer>> fieldRenames;
private final NameGen names;
public MetadataRoot() {
nameCounts = X_Collect.newStringMap(Integer.class);
fieldRenames = X_Collect.newStringDeepMap(NodeTransformer.class);
names = initNames();
}
protected NameGen initNames() {
return NameGen.getGlobal();
}
public String getRootReference() {
return rootReference;
}
public void setRootReference(String rootReference) {
this.rootReference = rootReference;
}
public String newVarName(String prefix) {
final Integer cnt;
synchronized (nameCounts) {
cnt = nameCounts.compute(prefix, (k, was) -> was == null ? 0 : was + 1);
}
if (cnt == 0) {
return prefix;
}
return prefix + "_" + cnt;
}
public void reserveName(String refName) {
Integer was = nameCounts.put(refName, 0);
assert was == null : "Tried to reserve a name, `" + refName + "` more than once\n" +
"Existing items: " + nameCounts;
}
public void registerFieldMapping(String ref, String fieldName, NodeTransformer accessor) {
fieldRenames.get(ref).put(fieldName, accessor);
}
public NodeTransformer findReplacement(String ref, String var) {
if (fieldRenames.containsKey(ref)) {
return fieldRenames.get(ref).get(var);
}
return null;
}
}
// WARNING: If you add new fields, update copyFrom method!
private MetadataRoot root;
protected final Fifo<SourceTransform> modifiers;
private StringTo<MethodBuffer> methods;
private SimpleLinkedList<String> panelNames;
private boolean allowedToFail;
private boolean sideEffects;
private UiContainerExpr container;
private ContainerMetadata parent;
private SourceBuilder<ContainerMetadata> sourceBuilder;
private Lazy<StyleMetadata> style = deferred1(StyleMetadata::new);
private IdentityHashMap<UiContainerExpr, ContainerMetadata> children;
private String elementType;
private String componentType;
private String controllerType;
private String type;
private boolean $thisPrinted;
private boolean $uiPrinted;
private boolean searchTypes;
private String controllerPkg;
private String controllerName;
// WARNING: If you add new fields, update copyFrom method!
protected void copyFrom(ContainerMetadata metadata, boolean child) {
this.allowedToFail = metadata.allowedToFail;
this.methods = metadata.methods;
this.panelNames = metadata.panelNames;
this.root = metadata.root;
this.elementType = metadata.elementType;
this.componentType = metadata.componentType;
this.controllerPkg = metadata.controllerPkg;
this.controllerType = metadata.controllerType;
this.controllerName = metadata.controllerName;
this.searchTypes = metadata.searchTypes;
if (!child) {
this.type = metadata.type;
this.style = metadata.style;
}
}
public ContainerMetadata() {
modifiers = newFifo();
searchTypes = true;
methods = X_Collect.newStringMap(MethodBuffer.class);
panelNames = new SimpleLinkedList<>();
children = new IdentityHashMap<>();
allowedToFail = Boolean.getBoolean("xapi.component.ignore.parse.failure");
}
public ContainerMetadata(UiContainerExpr container) {
this();
setContainer(container);
}
public void addModifier(SourceTransform transform) {
modifiers.give(transform);
}
protected Fifo<SourceTransform> newFifo() {
return X_Collect.newFifo();
}
public boolean isAllowedToFail() {
return allowedToFail;
}
public void setContainer(UiContainerExpr container) {
// Check the container for various interesting things, like method references.
this.container = container;
}
public UiContainerExpr getUi() {
return container;
}
public NameGen getNameGen() {
return root.names;
}
public StyleMetadata getStyle() {
return style.out1();
}
public boolean hasStyle() {
return style.isResolved();
}
public ContainerMetadata createChild(UiContainerExpr n, UiGeneratorTools tools) {
return children.computeIfAbsent(n, ui->{
final ContainerMetadata copy = tools.createMetadata(root, n);
copy.parent = this;
copy.copyFrom(this, true);
return copy;
});
}
public ContainerMetadata getParent() {
return parent;
}
public void recordSideEffects(UiGeneratorTools service, UiFeatureGenerator feature) {
recordSideEffects(service, this, feature);
}
public void recordSideEffects(UiGeneratorTools service, ContainerMetadata source, UiFeatureGenerator feature) {
setSideEffects(true);
if (parent != null) {
parent.recordSideEffects(service, source, feature);
}
}
public boolean isSideEffects() {
return sideEffects;
}
public void setSideEffects(boolean sideEffects) {
this.sideEffects = sideEffects;
}
public void applyModifiers(ClassBuffer out, String input) {
// TODO intelligent handling of multiple modifiers...
modifiers.out(modifier -> out.printlns(modifier.transform(input)));
}
public void saveMethod(String key, MethodBuffer method) {
final MethodBuffer was = methods.put(key, method);
assert was == null || was == method : "Attempting to reassign a method that already exists to key " + key +
", previous: " + was + "\n new: " + method;
}
public MethodBuffer getMethod(String key) {
return methods.get(key);
}
public MethodBuffer getMethod(String key, In1Out1<String, MethodBuffer> create) {
return methods.getOrCreate(key, create);
}
public void setSourceBuilder(SourceBuilder<ContainerMetadata> sourceBuilder) {
this.sourceBuilder = sourceBuilder;
}
public void setElementType(String elementType) {
this.elementType = elementType;
}
public String getElementType() {
return elementType;
}
public String getElementTypeImported() {
return sourceBuilder.addImport(elementType);
}
public void setComponentType(String componentType) {
this.componentType = componentType;
}
public String getComponentType() {
return componentType;
}
public String getControllerPackage() {
return controllerPkg;
}
public String getControllerSimpleName() {
return controllerName;
}
public void setControllerType(String pkgName, String simpleName) {
this.controllerType = X_Source.qualifiedName(pkgName, simpleName);
this.controllerPkg = pkgName;
this.controllerName = simpleName;
}
public String getControllerType() {
return controllerType;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public String getTypeImported() {
assert type.endsWith(sourceBuilder.addImport(type))
: "Bad type import: " + type;
return sourceBuilder.addImport(type);
}
public void ensure$this() {
ensure$ui();
if ($thisPrinted) {
return;
}
$thisPrinted = true;
String imported = getTypeImported();
addModifier(ele ->
imported + " $this = (" + imported + ") $ui.getHost(" + ele + ");"
);
}
public void ensure$ui() {
if ($uiPrinted) {
return;
}
$uiPrinted = true;
String service = sourceBuilder.addImport(UiService.class);
addModifier(ele ->
service + " $ui = " + service + ".getUiService();"
);
}
public SourceBuilder<ContainerMetadata> getSourceBuilder() {
return sourceBuilder == null ? getParent() == null ? null : getParent().getSourceBuilder() : sourceBuilder;
}
public void pushPanelName(String root) {
panelNames.add(root);
}
public String peekPanelName() {
return panelNames.tail();
}
public String popPanelName() {
return panelNames.pop();
}
public MethodBuffer removeMethod(String key) {
return methods.remove(key);
}
public MethodBuffer getParentMethod() {
String parent = peekPanelName();
final MethodBuffer method = getMethod(parent);
assert method != null : "No method named " + parent + " found in " + this;
return method;
}
public String getRefName() {
return getRefName("ref");
}
public String getRefName(String backup) {
final Optional<UiAttrExpr> refAttr = container.getAttribute("ref");
if (refAttr.isPresent()) {
String refName = ASTHelper.extractAttrValue(refAttr.get());
root.reserveName(refName);
return refName;
} else {
String genRef = newVarName(backup);
container.addAttribute(true, new UiAttrExpr("ref", new StringLiteralExpr(genRef)));
return genRef;
}
}
public void setRoot(MetadataRoot root) {
this.root = root;
}
public void queryContainer(ComponentMetadataQuery query) {
container.accept(new ComponentMetadataFinder(), query);
}
public String getRootReference() {
if (root.rootReference == null) {
ContainerMetadata seed = this;
while (seed.getParent() != null) {
seed = seed.getParent();
}
root.rootReference = seed.getRefName();
}
return root.rootReference;
}
public String newVarName(String prefix) {
// TODO: look up at parents for scoping...
return root.newVarName(prefix);
}
public boolean isSearchTypes() {
return searchTypes;
}
public void setSearchTypes(boolean searchTypes) {
this.searchTypes = searchTypes;
}
public void registerFieldProvider(String ref, String fieldName, NodeTransformer accessor) {
root.registerFieldMapping(ref, fieldName, accessor);
}
public NodeTransformer findReplacement(String ref, String var) {
return root.findReplacement(ref, var);
}
public ContainerMetadata createImplementation(Class<? extends UiImplementationGenerator> implType) {
final ContainerMetadata copy = new ContainerMetadata(container);
copy.copyFrom(this, false);
SimpleLinkedList<ContainerMetadata> all = new SimpleLinkedList<>();
all.add(copy);
children.entrySet().forEach(e->
copy.children.put(e.getKey(), e.getValue().createImplementation(implType))
);
return copy;
}
}