Add code generation functionality to the validator, and various updates to the generated code

This commit is contained in:
Grahame Grieve 2024-10-18 20:49:34 +08:00
parent 242bc02372
commit 000d4c7cbb
13 changed files with 562 additions and 115 deletions

View File

@ -346,36 +346,41 @@ public class PEBuilder {
ElementDefinition defn = list.get(i);
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
if (defn.getType().size() > 1) {
// DebugUtilities.breakpoint();
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
} else {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
}
i++;
}
} else {
res.add(pe);
i++;
}
} else {
res.add(pe);
i++;
}
} else {
i++;
@ -528,7 +533,7 @@ public class PEBuilder {
protected void populateByProfile(Base base, PEDefinition definition) {
if (definition.types().size() == 1) {
for (PEDefinition pe : definition.directChildren(true)) {
if (pe.fixedValue()) {
if (pe.hasFixedValue()) {
if (pe.definition().hasPattern()) {
base.setProperty(pe.schemaName(), pe.definition().getPattern());
} else {

View File

@ -39,6 +39,7 @@ import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.profilemodel.PEDefinition.PEDefinitionElementMode;
import org.hl7.fhir.utilities.Utilities;
@ -220,7 +221,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), false);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
@ -228,17 +229,22 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), allFixed);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
/**
* @return True if the element has a fixed value. This will always be false if fixedProps = false when the builder is created
*/
public boolean fixedValue() {
public boolean hasFixedValue() {
return definition.hasFixed() || definition.hasPattern();
}
public Type getFixedValue() {
return definition.hasFixed() ? definition.getFixed() : definition.getPattern();
}
protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed);
@Override
@ -289,7 +295,7 @@ public abstract class PEDefinition {
public boolean isList() {
return "*".equals(definition.getMax());
return "*".equals(definition.getMax()) || (Utilities.parseInt(definition.getMax(), 2) > 1);
}

View File

@ -33,22 +33,30 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4.profilemodel.PEBuilder;
import org.hl7.fhir.r4.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r4.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r4.profilemodel.PEDefinition;
import org.hl7.fhir.r4.profilemodel.PEInstance;
import org.hl7.fhir.r4.profilemodel.PEType;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
@ -73,7 +81,12 @@ public class PECodeGenerator {
private String doco;
private String url;
private boolean isResource;
private Set<String> unfixed = new HashSet<>();
private Set<String> enumNames = new HashSet<>();
private StringBuilder inits = new StringBuilder();
private StringBuilder fields = new StringBuilder();
private StringBuilder enums = new StringBuilder();
private StringBuilder load = new StringBuilder();
private StringBuilder save = new StringBuilder();
private StringBuilder clear = new StringBuilder();
@ -82,10 +95,10 @@ public class PECodeGenerator {
private StringBuilder hash = new StringBuilder();
public void genId() {
if (isResource) {
genField(true, "id", "String", "id", "", false, "");
genAccessors(true, false, "id", "String", "", "String", "String", "Id", "Ids", false, "", false);
genLoad(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null);
genSave(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, false, null);
genField(true, "id", "String", "id", "", false, "", 0, 1, null);
genAccessors(true, false, "id", "String", "", "String", "String", "Id", "Ids", false, "", false, false);
genLoad(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null, false);
genSave(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, false, null, false);
genClear(false, "id");
}
}
@ -106,15 +119,27 @@ public class PECodeGenerator {
w(b, " private static final String CANONICAL_URL = \""+url+"\";");
w(b);
}
if (enums.length() > 0) {
w(b, enums.toString());
}
w(b, fields.toString());
jdoc(b, "Parameter-less constructor. If you use this, the fixed values won't be filled out - they'll be missing. They'll be filled in if/when you call build, so they won't be missing from the resource, only from this particular object model", 2, true);
if (unfixed.isEmpty()) {
jdoc(b, "Parameter-less constructor.", 2, true);
} else {
jdoc(b, "Parameter-less constructor. If you use this, the fixed values on "+CommaSeparatedStringBuilder.join(",", unfixed)+" won't be filled out - they'll be missing. They'll be filled in if/when you call build, so they won't be missing from the resource, only from this particular object model", 2, true);
}
w(b, " public "+name+"() {");
w(b, " // todo");
if (inits.length() > 0) {
w(b, " initFixedValues();");
}
w(b, " }");
w(b);
if (isResource) {
jdoc(b, "Construct an instance of the object, and fill out all the fixed values ", 2, true);
w(b, " public "+name+"(IWorkerContext context) {");
if (inits.length() > 0) {
w(b, " initFixedValues();");
}
w(b, " workerContext = context;");
w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);");
w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, builder.createResource(CANONICAL_URL, false));");
@ -171,6 +196,12 @@ public class PECodeGenerator {
w(b, save.toString());
w(b, " }");
w(b);
if (inits.length() > 0) {
w(b, " private void initFixedValues() {");
w(b, inits.toString());
w(b, " }");
w(b);
}
w(b, accessors.toString());
w(b);
w(b, " public void clear() {");
@ -180,18 +211,127 @@ public class PECodeGenerator {
w(b, "}");
}
private String generateEnum(PEDefinition source, PEDefinition field) {
if (field.definition().hasBinding() && !field.hasFixedValue()) {
ElementDefinitionBindingComponent binding = field.definition().getBinding();
if (binding.getStrength() == org.hl7.fhir.r4.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) {
org.hl7.fhir.r4.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r4.model.ValueSet.class, binding.getValueSet());
if (vs != null) {
ValueSetExpansionOutcome vse = workerContext.expandVS(vs, false, false);
if (vse.isOk()) {
String baseName = Utilities.nmtokenize(Utilities.singularise(vs.getName()));
String name = baseName;
int c = 0;
while (enumNames.contains(name)) {
c++;
name = baseName+c;
}
w(enums, " public enum "+name+" {");
for (int i = 0; i < vse.getValueset().getExpansion().getContains().size(); i++) {
ValueSetExpansionContainsComponent cc = vse.getValueset().getExpansion().getContains().get(i);
String code = Utilities.nmtokenize(cc.getCode()).toUpperCase();
if (cc.getAbstract()) {
code = "_"+code;
}
cc.setUserData("java.code", code);
w(enums, " "+code+(i < vse.getValueset().getExpansion().getContains().size() - 1 ? "," : ";")+" // \""+cc.getDisplay()+"\" = "+cc.getSystem()+"#"+cc.getCode());
}
w(enums, "");
w(enums, " public static "+name+" fromCode(String s) {");
w(enums, " switch (s) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case \""+cc.getCode()+"\": return "+cc.getUserString("java.code")+";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public static "+name+" fromCoding(Coding c) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
if (cc.hasVersion()) {
w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode()) && (!c.hasVersion() || \""+cc.getVersion()+"\".equals(c.getVersion()))) {");
} else {
w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode())) {");
}
w(enums, " return "+cc.getUserString("java.code")+";");
w(enums, " }");
}
w(enums, " return null;");
w(enums, " }");
w(enums, "");
w(enums, " public static "+name+" fromCodeableConcept(CodeableConcept cc) {");
w(enums, " for (Coding c : cc.getCoding()) {");
w(enums, " "+name+" v = fromCoding(c);");
w(enums, " if (v != null) {");
w(enums, " return v;");
w(enums, " }");
w(enums, " }");
w(enums, " return null;");
w(enums, " }");
w(enums, "");
w(enums, " public String toDisplay() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case "+cc.getUserString("java.code")+": return \""+Utilities.escapeJava(cc.getDisplay())+"\";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public String toCode() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case "+cc.getUserString("java.code")+": return \""+cc.getCode()+"\";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public Coding toCoding() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
if (cc.hasVersion()) {
w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setVersion(\""+cc.getVersion()+"\").setCode()\""+cc.getCode()+"\";");
} else {
w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setCode(\""+cc.getCode()+"\");");
}
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public CodeableConcept toCodeableConcept() {");
w(enums, " Coding c = toCoding();");
w(enums, " return c == null ? null : new CodeableConcept().addCoding(c);");
w(enums, " }");
w(enums, " }");
return name;
}
}
}
}
return null;
}
private void defineField(PEDefinition source, PEDefinition field) {
if (field.types().size() == 1) {
StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl());
if (sd != null) {
String enumName = generateEnum(source, field);
boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
boolean isAbstract = sd.getAbstract();
String name = field.name().replace("[x]", "");
String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false);
String sname = name;
String type = null;
String init = "";
String ptype = type;
if (isPrim) {
boolean isEnum = false;
if (enumName != null) {
type = enumName;
ptype = enumName;
isEnum = true;
} else if (isPrim) {
// todo: are we extension-less?
type = Utilities.capitalize(field.types().get(0).getName()+"Type");
ptype = getPrimitiveType(sd);
@ -210,16 +350,18 @@ public class PECodeGenerator {
String csname = Utilities.capitalize(sname);
String nn = field.min() == 1 ? "// @NotNull" : "";
boolean isExtension = field.isExtension();
genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation());
genAccessors(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.fixedValue());
genLoad(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.fixedValue(), field.types().get(0));
genSave(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.fixedValue(), isExtension, field.types().get(0));
genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation(), field.min(), field.max(), field.definition());
if (isPrim && field.hasFixedValue()) {
genFixed(name, ptype, field.getFixedValue());
}
genAccessors(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.hasFixedValue(), isEnum);
genLoad(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), field.types().get(0), isEnum);
genSave(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), isExtension, field.types().get(0), isEnum);
genClear(field.isList(), name);
}
} else {
// ignoring polymorphics for now
}
}
private void genClear(boolean list, String name) {
@ -230,11 +372,21 @@ public class PECodeGenerator {
}
}
private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo) {
private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo, boolean isEnum) {
if (isList) {
w(load, " for (PEInstance item : src.children(\""+sname+"\")) {");
w(load, " "+name+".add(("+type+") item.asDataType());");
w(load, " }");
} else if (isEnum) {
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("CodeableConcept".equals(typeInfo.getName())) {
w(load, " "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+name+"\").asDataType());");
} else if ("Coding".equals(typeInfo.getName())) {
w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+name+"\").asDataType());");
} else {
w(load, " "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType()).primitiveValue());");
}
w(load, " }");
} else if (isPrim) {
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("CodeType".equals(type)) {
@ -255,7 +407,7 @@ public class PECodeGenerator {
}
}
private void genSave(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, boolean isExtension, PEType typeInfo) {
private void genSave(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, boolean isExtension, PEType typeInfo, boolean isEnum) {
w(save, " tgt.clear(\""+sname+"\");");
if (isList) {
w(save, " for ("+type+" item : "+name+") {");
@ -265,6 +417,16 @@ public class PECodeGenerator {
w(save, " tgt.addChild(\""+sname+"\", item);");
}
w(save, " }");
} else if (isEnum) {
w(save, " if ("+name+" != null) {");
if ("CodeableConcept".equals(typeInfo.getName())) {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCodeableConcept());");
} else if ("Coding".equals(typeInfo.getName())) {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCoding());");
} else {
w(save, " tgt.addChild(\""+sname+"\", "+name+").toCode();");
}
w(save, " }");
} else if (isPrim) {
w(save, " if ("+name+" != null) {");
if (isExtension) {
@ -290,21 +452,27 @@ public class PECodeGenerator {
}
}
private void genAccessors(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed) {
private void genAccessors(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed, boolean isEnum) {
jdoc(accessors, doco, 2, true);
if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
w(accessors, " public "+ptype+" get"+cname+"() {");
w(accessors, " return "+name+";");
w(accessors, " }");
w(accessors);
w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {");
w(accessors, " this."+name+" = value;");
w(accessors, " return this;");
w(accessors, " }");
w(accessors);
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return "+name+" != null;");
w(accessors, " }");
if (isFixed) {
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return true;");
w(accessors, " }");
} else {
w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {");
w(accessors, " this."+name+" = value;");
w(accessors, " return this;");
w(accessors, " }");
w(accessors);
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return "+name+" != null;");
w(accessors, " }");
}
} else {
if (isPrim && !isList) {
w(accessors, " public "+ptype+" get"+cname+"() {");
@ -379,8 +547,27 @@ public class PECodeGenerator {
w(accessors);
}
private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco) {
// jdoc(fields, field.documentation(), 2, true);
private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) {
// jdoc(fields, shortDoco, 2, true);
w(fields, " @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")"));
if (ed != null) {
if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
w(fields, " @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")");
}
if (ed.getMustSupport()) {
w(fields, " @MustSupport(true)");
}
if (ed.hasLabel() || ed.hasDefinition()) {
String s = "";
if (ed.hasLabel()) {
s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")";
}
if (ed.hasDefinition()) {
s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")";
}
w(fields, " "+s);
}
}
if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco);
} else if (isList) {
@ -388,6 +575,17 @@ public class PECodeGenerator {
} else {
w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco);
}
w(fields, "");
}
private void genFixed(String name, String pType, Type fixedValue) {
if ("String".equals(pType)) {
w(inits, " "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";");
} else {
unfixed.add(name);
System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString());
}
}
}
@ -395,6 +593,7 @@ public class PECodeGenerator {
private IWorkerContext workerContext;
private String canonical;
private String pkgName;
private String version;
// options:
private ExtensionPolicy extensionPolicy;
@ -421,6 +620,14 @@ public class PECodeGenerator {
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getCanonical() {
return canonical;
}
@ -500,19 +707,31 @@ public class PECodeGenerator {
* @throws IOException
*
*/
public void execute() throws IOException {
public String execute() throws IOException {
imports = new StringBuilder();
PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical);
w(imports, "import java.util.List;");
w(imports, "import java.util.ArrayList;");
w(imports, "import javax.annotation.Nullable;");
w(imports, "import java.util.Date;\r\n");
w(imports);
w(imports, "import org.hl7.fhir.r5.context.IWorkerContext;");
w(imports, "import org.hl7.fhir.r5.model.*;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;");
w(imports, "import org.hl7.fhir."+version+".model.*;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;");
PEGenClass cls = genClass(source);
StringBuilder b = new StringBuilder();
w(b, "package "+pkgName+";");
@ -523,6 +742,7 @@ public class PECodeGenerator {
w(b, imports.toString());
cls.write(b, source.getProfile().getCopyright());
TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java"));
return cls.name+".java";
}
public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) {

View File

@ -112,6 +112,10 @@ public interface ValueSetExpander {
return this;
}
public boolean isOk() {
return error == null;
}
}
/**

View File

@ -50,6 +50,7 @@ import org.hl7.fhir.r5.model.ResourceFactory;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.DebugUtilities;
import org.hl7.fhir.utilities.Utilities;
/**
@ -349,36 +350,41 @@ public class PEBuilder {
ElementDefinition defn = list.get(i);
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
if (defn.getType().size() > 1) {
// DebugUtilities.breakpoint();
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
} else {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
}
i++;
}
} else {
res.add(pe);
i++;
}
} else {
res.add(pe);
i++;
}
} else {
i++;

View File

@ -222,7 +222,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), false);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
@ -230,7 +230,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), allFixed);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}

View File

@ -35,6 +35,7 @@ import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.DebugUtilities;
public class PEDefinitionElement extends PEDefinition {

View File

@ -60,6 +60,29 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
/**
*
* The easiest way to generate code is to use the FHIR Validator, which can generate java classes for profiles
* using this code. Parameters:
*
* -codegen -version r4 -ig hl7.fhir.dk.core#3.2.0 -profiles http://hl7.dk/fhir/core/StructureDefinition/dk-core-gln-identifier,http://hl7.dk/fhir/core/StructureDefinition/dk-core-patient -output /Users/grahamegrieve/temp/codegen -package-name org.hl7.fhir.test
*
* Parameter Documentation:
* -codegen: tells the validator to generate code
* -version {r4|5}: which version to generate for
* -ig {name}: loads an IG (and it's dependencies) - see -ig documentation for the validator
* -profiles {list}: a comma separated list of profile URLs to generate code for
* -output {folder}: the folder where to generate the output java class source code
* -package-name {name}: the name of the java package to generate in
*
* options
* -option {name}: a code generation option, one of:
*
* narrative: generate code for the resource narrative (recommended: don't - leave that for the native resource level)
* meta: generate code the what's in meta
* contained: generate code for contained resources
* all-elements: generate code for all elements, not just the key elements (makes the code verbose)
*/
public class PECodeGenerator {
@ -321,7 +344,7 @@ public class PECodeGenerator {
String enumName = generateEnum(source, field);
boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
boolean isAbstract = sd.getAbstract();
String name = field.name().replace("[x]", "");
String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false);
String sname = name;
String type = null;
String init = "";
@ -593,6 +616,7 @@ public class PECodeGenerator {
private IWorkerContext workerContext;
private String canonical;
private String pkgName;
private String version;
// options:
private ExtensionPolicy extensionPolicy;
@ -619,6 +643,14 @@ public class PECodeGenerator {
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getCanonical() {
return canonical;
}
@ -698,7 +730,7 @@ public class PECodeGenerator {
* @throws IOException
*
*/
public void execute() throws IOException {
public String execute() throws IOException {
imports = new StringBuilder();
PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical);
@ -707,20 +739,20 @@ public class PECodeGenerator {
w(imports, "import javax.annotation.Nullable;");
w(imports, "import java.util.Date;\r\n");
w(imports);
w(imports, "import org.hl7.fhir.r5.context.IWorkerContext;");
w(imports, "import org.hl7.fhir.r5.model.*;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Definition;");
w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;");
w(imports, "import org.hl7.fhir."+version+".model.*;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;");
PEGenClass cls = genClass(source);
@ -733,6 +765,7 @@ public class PECodeGenerator {
w(b, imports.toString());
cls.write(b, source.getProfile().getCopyright());
TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java"));
return cls.name+".java";
}
public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) {

View File

@ -90,6 +90,8 @@ public class CliContext {
private String snomedCT = "900000000000207008";
@JsonProperty("targetVer")
private String targetVer = null;
@JsonProperty("packageName")
private String packageName = null;
@JsonProperty("noEcosystem")
private boolean noEcosystem = false;
@ -101,6 +103,8 @@ public class CliContext {
private QuestionnaireMode questionnaireMode = QuestionnaireMode.CHECK;
@JsonProperty("level")
private ValidationLevel level = ValidationLevel.HINTS;
@JsonProperty("options")
private List<String> options = new ArrayList<String>();
@JsonProperty("profiles")
private List<String> profiles = new ArrayList<String>();
@ -435,6 +439,25 @@ public class CliContext {
return this;
}
@JsonProperty("options")
public List<String> getOptions() {
return options;
}
@JsonProperty("options")
public CliContext setOptions(List<String> options) {
this.options = options;
return this;
}
public CliContext addOption(String option) {
if (this.options == null) {
this.options = new ArrayList<>();
}
this.options.add(option);
return this;
}
@JsonProperty("mode")
public EngineMode getMode() {
return mode;
@ -638,6 +661,17 @@ public class CliContext {
return this;
}
@JsonProperty("packageName")
public String getPackageName() {
return packageName;
}
@JsonProperty("packageName")
public CliContext setPackageName(String packageName) {
this.packageName = packageName;
return this;
}
@JsonProperty("doDebug")
public boolean isDoDebug() {
return doDebug;
@ -857,10 +891,12 @@ public class CliContext {
Objects.equals(fhirpath, that.fhirpath) &&
Objects.equals(snomedCT, that.snomedCT) &&
Objects.equals(targetVer, that.targetVer) &&
Objects.equals(packageName, that.packageName) &&
Objects.equals(igs, that.igs) &&
Objects.equals(questionnaireMode, that.questionnaireMode) &&
Objects.equals(level, that.level) &&
Objects.equals(profiles, that.profiles) &&
Objects.equals(options, that.options) &&
Objects.equals(sources, that.sources) &&
Objects.equals(crumbTrails, that.crumbTrails) &&
Objects.equals(showMessageIds, that.showMessageIds) &&
@ -885,7 +921,7 @@ public class CliContext {
public int hashCode() {
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
targetVer, packageName, igs, questionnaireMode, level, profiles, options, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, advisorFile, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
}
@ -921,10 +957,12 @@ public class CliContext {
", fhirpath='" + fhirpath + '\'' +
", snomedCT='" + snomedCT + '\'' +
", targetVer='" + targetVer + '\'' +
", packageName='" + packageName + '\'' +
", igs=" + igs +
", questionnaireMode=" + questionnaireMode +
", level=" + level +
", profiles=" + profiles +
", options=" + options +
", sources=" + sources +
", inputs=" + inputs +
", mode=" + mode +

View File

@ -35,6 +35,8 @@ import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r5.renderers.spreadsheets.CodeSystemSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.ConceptMapSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.StructureDefinitionSpreadsheetGenerator;
@ -73,6 +75,8 @@ import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import org.hl7.fhir.validation.instance.advisor.JsonDrivenPolicyAdvisor;
import org.hl7.fhir.validation.instance.advisor.TextDrivenPolicyAdvisor;
import kotlin.NotImplementedError;
public class ValidationService {
private final SessionCache sessionCache;
@ -827,4 +831,52 @@ public class ValidationService {
}
}
public void codeGen(CliContext cliContext, ValidationEngine validationEngine) throws IOException {
boolean ok = true;
if (cliContext.getProfiles().isEmpty()) {
System.out.println("Must specify at least one profile to generate code for with -profile or -profiles ");
ok = false;
}
if (cliContext.getPackageName() == null) {
System.out.println("Must provide a Java package name (-package-name)");
ok = false;
}
if (cliContext.getSv() == null) {
System.out.println("Must specify a version (-version)");
ok = false;
} else if (!VersionUtilities.isR4Ver(cliContext.getSv()) && !VersionUtilities.isR5Ver(cliContext.getSv())) {
System.out.println("Only versions 4 and 5 are supported (-version)");
ok = false;
}
if (cliContext.getOutput() == null) {
System.out.println("Must provide an output directory (-output)");
ok = false;
}
if (ok) {
PECodeGenerator gen = new PECodeGenerator(validationEngine.getContext());
gen.setFolder(cliContext.getOutput());
gen.setExtensionPolicy(ExtensionPolicy.Complexes);
gen.setNarrative(cliContext.getOptions().contains("narrative"));
gen.setMeta(cliContext.getOptions().contains("meta"));
gen.setLanguage(Locale.getDefault().toLanguageTag());
gen.setContained(cliContext.getOptions().contains("contained"));
gen.setKeyElementsOnly(!cliContext.getOptions().contains("all-elements"));
gen.setGenDate(new SimpleDateFormat().format(new Date()));
gen.setPkgName(cliContext.getPackageName());
if (VersionUtilities.isR4Ver(cliContext.getSv())) {
gen.setVersion("r4");
} else {
gen.setVersion("r5");
}
for (String profile : cliContext.getProfiles()) {
gen.setCanonical(profile);
System.out.print("Generate for "+profile);
String s = gen.execute();
System.out.println(": "+s);
}
System.out.println("Done");
}
}
}

View File

@ -0,0 +1,43 @@
package org.hl7.fhir.validation.cli.tasks;
import java.io.PrintStream;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.services.ValidationService;
import org.hl7.fhir.validation.cli.utils.EngineMode;
public class CodeGenTask extends ValidationEngineTask {
@Override
public String getName() {
return "codegen";
}
@Override
public String getDisplayName() {
return "Code Generation";
}
@Override
public boolean isHidden() {
return true;
}
@Override
public boolean shouldExecuteTask(CliContext cliContext, String[] args) {
return cliContext.getMode() == EngineMode.CODEGEN;
}
@Override
public void printHelp(PrintStream out) {
}
@Override
public void executeTask(ValidationService validationService, ValidationEngine validationEngine, CliContext cliContext, String[] args, TimeTracker tt, TimeTracker.Session tts) throws Exception {
validationService.codeGen(cliContext, validationEngine);
}
}

View File

@ -13,5 +13,6 @@ public enum EngineMode {
FHIRPATH,
VERSION,
RUN_TESTS,
INSTALL
INSTALL,
CODEGEN
}

View File

@ -30,6 +30,9 @@ public class Params {
public static final String HTTPS_PROXY = "-https-proxy";
public static final String PROXY_AUTH = "-auth";
public static final String PROFILE = "-profile";
public static final String PROFILES = "-profiles";
public static final String OPTION = "-option";
public static final String OPTIONS = "-options";
public static final String BUNDLE = "-bundle";
public static final String QUESTIONNAIRE = "-questionnaire";
public static final String NATIVE = "-native";
@ -42,9 +45,11 @@ public class Params {
public static final String EXTENSION = "-extension";
public static final String HINT_ABOUT_NON_MUST_SUPPORT = "-hintAboutNonMustSupport";
public static final String TO_VERSION = "-to-version";
public static final String PACKAGE_NAME = "-package-name";
public static final String DO_NATIVE = "-do-native";
public static final String NO_NATIVE = "-no-native";
public static final String COMPILE = "-compile";
public static final String CODEGEN = "-codegen";
public static final String TRANSFORM = "-transform";
public static final String LANG_TRANSFORM = "-lang-transform";
public static final String NARRATIVE = "-narrative";
@ -184,11 +189,39 @@ public class Params {
} else if (args[i].equals(PROFILE)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -profile without indicating profile source");
throw new Error("Specified -profile without indicating profile url");
} else {
p = args[++i];
cliContext.addProfile(p);
}
} else if (args[i].equals(PROFILES)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -profiles without indicating profile urls");
} else {
p = args[++i];
for (String s : p.split("\\,")) {
cliContext.addProfile(s);
}
}
} else if (args[i].equals(OPTION)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -option without indicating option value");
} else {
p = args[++i];
cliContext.addOption(p);
}
} else if (args[i].equals(OPTIONS)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -options without indicating option values");
} else {
p = args[++i];
for (String s : p.split("\\,")) {
cliContext.addOption(s);
}
}
} else if (args[i].equals(BUNDLE)) {
String profile = null;
String rule = null;
@ -294,6 +327,9 @@ public class Params {
} else if (args[i].equals(TO_VERSION)) {
cliContext.setTargetVer(args[++i]);
cliContext.setMode(EngineMode.VERSION);
} else if (args[i].equals(PACKAGE_NAME)) {
cliContext.setPackageName(args[++i]);
cliContext.setMode(EngineMode.CODEGEN);
} else if (args[i].equals(DO_NATIVE)) {
cliContext.setCanDoNative(true);
} else if (args[i].equals(NO_NATIVE)) {
@ -307,6 +343,8 @@ public class Params {
} else if (args[i].equals(COMPILE)) {
cliContext.setMap(args[++i]);
cliContext.setMode(EngineMode.COMPILE);
} else if (args[i].equals(CODEGEN)) {
cliContext.setMode(EngineMode.CODEGEN);
} else if (args[i].equals(NARRATIVE)) {
cliContext.setMode(EngineMode.NARRATIVE);
} else if (args[i].equals(SPREADSHEET)) {