more improvements to profile generation

This commit is contained in:
Grahame Grieve 2023-10-24 17:36:23 +11:00
parent f3ddf1a0f4
commit be9f5e0d36
20 changed files with 590 additions and 84 deletions

View File

@ -881,7 +881,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (pIn == null) {
throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS));
}
if (vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-time-units") || vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-distance-units")) {
if (vs.hasUrl() && (vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-time-units") || vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-distance-units"))) {
return new ValueSetExpansionOutcome("This value set is not expanded correctly at this time (will be fixed in a future version)", TerminologyServiceErrorClass.VALUESET_UNSUPPORTED, false);
}
@ -1792,6 +1792,22 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (uri == null) {
return null;
}
if (uri.startsWith("#")) {
if (sourceForReference != null && sourceForReference instanceof DomainResource) {
for (Resource r : ((DomainResource) sourceForReference).getContained()) {
if (r.getClass() == class_ &&( "#"+r.getIdBase()).equals(uri)) {
if (r instanceof CanonicalResource) {
CanonicalResource cr = (CanonicalResource) r;
if (!cr.hasUrl()) {
cr.setUrl(Utilities.makeUuidUrn());
}
}
return (T) r;
}
}
}
return null;
}
if (QA_CHECK_REFERENCE_SOURCE) {
// it can be tricky to trace the source of a reference correctly. The code isn't water tight,

View File

@ -531,7 +531,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

@ -37,6 +37,7 @@ import org.apache.xmlbeans.impl.xb.xsdschema.All;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -236,10 +237,15 @@ public abstract class PEDefinition {
/**
* @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 DataType getFixedValue() {
return definition.hasFixed() ? definition.getFixed() : definition.getPattern();
}
protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed);
@Override
@ -290,7 +296,7 @@ public abstract class PEDefinition {
public boolean isList() {
return "*".equals(definition.getMax());
return "*".equals(definition.getMax()) || (Utilities.parseInt(definition.getMax(), 2) > 1);
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface BindingStrength {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface Definition {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface Doco {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface Label {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface Max {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface Min {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface MustSupport {
boolean value();
}

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.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Observation;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.profilemodel.PEDefinition;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.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,10 +211,114 @@ 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.r5.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) {
org.hl7.fhir.r5.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r5.model.ValueSet.class, binding.getValueSet(), field.getProfile());
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]", "");
@ -191,7 +326,12 @@ public class PECodeGenerator {
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,18 +350,20 @@ 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) {
if (list) {
w(clear, " "+name+".clear();");
@ -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, DataType 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());
}
}
}
@ -501,6 +699,8 @@ public class PECodeGenerator {
*
*/
public void 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;");
@ -513,6 +713,16 @@ public class PECodeGenerator {
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;");
PEGenClass cls = genClass(source);
StringBuilder b = new StringBuilder();
w(b, "package "+pkgName+";");

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r5.profilemodel.gen;
public @interface ValueSet {
String value();
}

View File

@ -70,7 +70,7 @@ import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
public class CodeSystemUtilities {
public class CodeSystemUtilities extends TerminologyUtilities {
public static class SystemReference {
private String link;

View File

@ -0,0 +1,27 @@
package org.hl7.fhir.r5.terminologies;
import java.util.HashSet;
import java.util.Set;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.utilities.json.model.JsonObject;
public class TerminologyUtilities {
public static Set<String> listOids(CanonicalResource cr) {
Set<String> oids = new HashSet<>();
if (cr.hasUrl() && cr.getUrl().startsWith("urn:oid:")) {
oids.add(cr.getUrl().substring(8));
}
for (Identifier id : cr.getIdentifier()) {
String v = id.getValue();
if (v != null && v.startsWith("urn:oid:")) {
oids.add(v.substring(8));
}
}
return oids;
}
}

View File

@ -3,7 +3,9 @@ package org.hl7.fhir.r5.terminologies;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
Copyright (c) 2011+, HL7, Inc.
@ -66,7 +68,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
public class ValueSetUtilities {
public class ValueSetUtilities extends TerminologyUtilities {
public static boolean isServerSide(String url) {
@ -401,5 +403,20 @@ public class ValueSetUtilities {
return i;
}
public static Set<String> listSystems(IWorkerContext ctxt, ValueSet vs) {
Set<String> systems = new HashSet<>();
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
for (CanonicalType ct : inc.getValueSet()) {
ValueSet vsr = ctxt.fetchResource(ValueSet.class, ct.asStringValue(), vs);
if (vsr != null) {
systems.addAll(listSystems(ctxt, vsr));
}
}
if (inc.hasSystem()) {
systems.add(inc.getSystem());
}
}
return systems;
}
}

View File

@ -112,7 +112,7 @@ public class PETests {
checkElement(children.get(7), "extension", "complex", 0, 1, false, "http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex", 4, "extension('http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')");
checkElement(children.get(8), "identifier", "identifier", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Identifier", 7, "identifier");
checkElement(children.get(9), "status", "status", 1, 1, true, "http://hl7.org/fhir/StructureDefinition/code", 2, "status");
checkElement(children.get(10), "category", "category", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/CodeableConcept", 3, "category");
checkElement(children.get(10), "category", "category", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/CodeableConcept", 3, "category");
checkElement(children.get(11), "code", "code", 1, 1, true, "http://hl7.org/fhir/StructureDefinition/CodeableConcept", 3, "code");
checkElement(children.get(12), "subject", "subject", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/Reference", 5, "subject");
checkElement(children.get(13), "encounter", "encounter", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Reference", 5, "encounter");
@ -154,7 +154,7 @@ public class PETests {
Assertions.assertEquals(schemaName, pe.schemaName());
Assertions.assertEquals(min, pe.min());
Assertions.assertEquals(max, pe.max());
Assertions.assertEquals(fixed, pe.fixedValue() || pe.isInFixedValue());
Assertions.assertEquals(fixed, pe.hasFixedValue() || pe.isInFixedValue());
if (type != null) {
Assertions.assertEquals(1, pe.types().size());
Assertions.assertEquals(type, pe.types().get(0).getUrl());

View File

@ -12,33 +12,17 @@ import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
import java.util.List;
import java.util.ArrayList;
import javax.annotation.Nullable;
import java.util.Date;
import org.hl7.fhir.r5.profilemodel.gen.Min;
import org.hl7.fhir.r5.profilemodel.gen.Max;
import org.hl7.fhir.r5.profilemodel.gen.Label;
import org.hl7.fhir.r5.profilemodel.gen.Doco;
import org.hl7.fhir.r5.profilemodel.gen.BindingStrength;
import org.hl7.fhir.r5.profilemodel.gen.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.MustSupport;
import org.hl7.fhir.r5.profilemodel.gen.Definition;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
import java.util.List;
import java.util.ArrayList;
import javax.annotation.Nullable;
import java.util.Date;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
// Generated by the HAPI Java Profile Generator, Tue, Sep 26, 2023 00:00+1000
// Generated by the HAPI Java Profile Generator, {date}
/**
* A complex extension - an extension with 2 levels
@ -48,9 +32,22 @@ public class TestComplexExtension extends PEGeneratedBase {
private static final String CANONICAL_URL = "http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex|0.1";
@Min("1") @Max("*") @Doco("Additional content defined by implementations")
@Definition("May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and managable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.")
private List<Extension> extensions = new ArrayList<>();// @NotNull // Additional content defined by implementations
@Min("0") @Max("2") @Doco("A code")
@MustSupport(true)
@Definition("A code for the extension")
private Coding slice1; // A code
@Min("0") @Max("*") @Doco("More Details")
@Definition("More details")
private List<StringType> slice2s = new ArrayList<>(); // More Details
@Min("1") @Max("1") @Doco("Justification Details")
@MustSupport(true)
@Definition("Justification Details.")
private Extension slice3;// @NotNull // Justification Details
/**

View File

@ -12,21 +12,17 @@ import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
import java.util.List;
import java.util.ArrayList;
import javax.annotation.Nullable;
import java.util.Date;
import org.hl7.fhir.r5.profilemodel.gen.Min;
import org.hl7.fhir.r5.profilemodel.gen.Max;
import org.hl7.fhir.r5.profilemodel.gen.Label;
import org.hl7.fhir.r5.profilemodel.gen.Doco;
import org.hl7.fhir.r5.profilemodel.gen.BindingStrength;
import org.hl7.fhir.r5.profilemodel.gen.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.MustSupport;
import org.hl7.fhir.r5.profilemodel.gen.Definition;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
// Generated by the HAPI Java Profile Generator, Sun, Aug 20, 2023 19:05+1000
// Generated by the HAPI Java Profile Generator, {date}
/**
* Test CodeableConcept Profile.
@ -36,11 +32,23 @@ public class TestDatatypeProfile extends PEGeneratedBase {
private static final String CANONICAL_URL = "http://hl7.org/fhir/test/StructureDefinition/pe-profile2|0.1";
@Min("1") @Max("2") @Doco("Code defined by a terminology system")
@Definition("A reference to a code defined by a terminology system.")
private Coding coding;// @NotNull // Code defined by a terminology system
@Min("1") @Max("1") @Doco("Code defined by a terminology system")
@Definition("A reference to a code defined by a terminology system.")
private Coding snomedct;// @NotNull // Code defined by a terminology system
@Min("0") @Max("1") @Doco("Code defined by a terminology system")
@Definition("A reference to a code defined by a terminology system.")
private Coding loinc; // Code defined by a terminology system
@Min("1") @Max("1") @Doco("Plain text representation of the concept")
@Definition("A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.")
private String text;// @NotNull // Plain text representation of the concept
/**
* 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

View File

@ -24,9 +24,23 @@ import org.hl7.fhir.r5.profilemodel.PEBuilder;
import org.hl7.fhir.r5.profilemodel.PEInstance;
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;
import org.hl7.fhir.r5.profilemodel.gen.Min;
import org.hl7.fhir.r5.profilemodel.gen.Max;
import org.hl7.fhir.r5.profilemodel.gen.Label;
import org.hl7.fhir.r5.profilemodel.gen.Doco;
import org.hl7.fhir.r5.profilemodel.gen.BindingStrength;
import org.hl7.fhir.r5.profilemodel.gen.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.MustSupport;
import org.hl7.fhir.r5.profilemodel.gen.Definition;
// Generated by the HAPI Java Profile Generator, Sun, Aug 20, 2023 19:01+1000
/*
Licensed under CC0 1.0 Universal (CC0 1.0).
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below.
*/
// Generated by the HAPI Java Profile Generator, {date}
/**
* Test Observation Profile.
@ -36,28 +50,132 @@ public class TestProfile extends PEGeneratedBase {
private static final String CANONICAL_URL = "http://hl7.org/fhir/test/StructureDefinition/pe-profile1|0.1";
public enum ProfileObservationCategoryCode {
LABORATORY, // "Laboratory" = http://terminology.hl7.org/CodeSystem/observation-category#laboratory
IMAGING; // "Imaging" = http://terminology.hl7.org/CodeSystem/observation-category#imaging
public static ProfileObservationCategoryCode fromCode(String s) {
switch (s) {
case "laboratory": return LABORATORY;
case "imaging": return IMAGING;
default: return null;
}
}
public static ProfileObservationCategoryCode fromCoding(Coding c) {
if ("http://terminology.hl7.org/CodeSystem/observation-category".equals(c.getSystem()) && "laboratory".equals(c.getCode())) {
return LABORATORY;
}
if ("http://terminology.hl7.org/CodeSystem/observation-category".equals(c.getSystem()) && "imaging".equals(c.getCode())) {
return IMAGING;
}
return null;
}
public static ProfileObservationCategoryCode fromCodeableConcept(CodeableConcept cc) {
for (Coding c : cc.getCoding()) {
ProfileObservationCategoryCode v = fromCoding(c);
if (v != null) {
return v;
}
}
return null;
}
public String toDisplay() {
switch (this) {
case LABORATORY: return "Laboratory";
case IMAGING: return "Imaging";
default: return null;
}
}
public String toCode() {
switch (this) {
case LABORATORY: return "laboratory";
case IMAGING: return "imaging";
default: return null;
}
}
public Coding toCoding() {
switch (this) {
case LABORATORY: return new Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("laboratory");
case IMAGING: return new Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("imaging");
default: return null;
}
}
public CodeableConcept toCodeableConcept() {
Coding c = toCoding();
return c == null ? null : new CodeableConcept().addCoding(c);
}
}
@Min("0") @Max("1") @Doco("")
private String id; //
@Min("0") @Max("*") @Doco("Extension")
@Definition("An Extension")
private List<Extension> extensions = new ArrayList<>(); // Extension
@Min("0") @Max("1") @Doco("A simple extension")
@Definition("A simple extension - an extension with just a value")
private String simple; // A simple extension
// @ProfileAnnotation(max = 1, min=1, path="Observation.extension('url')", doco = "blah", type="")
@Min("0") @Max("1") @Doco("A complex extension")
@Definition("A complex extension - an extension with 2 levels")
private TestComplexExtension complex; // A complex extension
@Min("0") @Max("1") @Doco("Business Identifier for observation")
@Definition("A unique identifier assigned to this observation.")
private Identifier identifier; // Business Identifier for observation
@Min("1") @Max("1") @Doco("registered | preliminary | final | amended +")
@BindingStrength("required") @ValueSet("http://hl7.org/fhir/ValueSet/observation-status|5.0.0")
@Definition("The status of the result value.")
private String status;// @NotNull // registered | preliminary | final | amended +
@Min("1") @Max("1") @Doco("Classification of type of observation")
@BindingStrength("required") @ValueSet("#vs1")
@Definition("A code that classifies the general type of observation being made.")
private ProfileObservationCategoryCode category;// @NotNull // Classification of type of observation
@Min("1") @Max("1") @Doco("Sexual Orientation")
@BindingStrength("example") @ValueSet("http://hl7.org/fhir/ValueSet/observation-codes")
@Definition("Describes what was observed. Sometimes this is called the observation \"name\".")
private CodeableConcept code;// @NotNull // Sexual Orientation
@Min("1") @Max("1") @Doco("Who and/or what the observation is about")
@MustSupport(true)
@Definition("The patient, or group of patients, location, device, organization, procedure or practitioner this observation is about and into whose or what record the observation is placed. If the actual focus of the observation is different from the subject (or a sample of, part, or region of the subject), the `focus` element or the `code` itself specifies the actual focus of the observation.")
private Reference subject;// @NotNull // Who and/or what the observation is about
@Min("0") @Max("1") @Doco("Healthcare event during which this observation is made")
@Definition("The healthcare event (e.g. a patient and healthcare provider interaction) during which this observation is made.")
private Reference encounter; // Healthcare event during which this observation is made
@Min("1") @Max("1") @Doco("Clinically relevant time/time-period for observation")
@Definition("Time of observation")
private Date effective;// @NotNull // Clinically relevant time/time-period for observation
@Min("0") @Max("*") @Doco("Who is responsible for the observation")
@Definition("Who was responsible for asserting the observed value as \"true\".")
private List<Reference> performers = new ArrayList<>(); // Who is responsible for the observation
@Min("0") @Max("1") @Doco("Sexual Orientation")
@BindingStrength("extensible") @ValueSet("http://hl7.org/fhir/us/core/ValueSet/us-core-sexual-orientation")
@MustSupport(true)
@Definition("The Sexual Orientation value.")
private TestDatatypeProfile valueCodeableConcept; // Sexual Orientation
/**
* 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
* Parameter-less constructor.
*
*/
public TestProfile() {
// todo
initFixedValues();
}
/**
@ -65,6 +183,7 @@ public class TestProfile extends PEGeneratedBase {
*
*/
public TestProfile(IWorkerContext context) {
initFixedValues();
workerContext = context;
PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);
PEInstance src = builder.buildPEInstance(CANONICAL_URL, builder.createResource(CANONICAL_URL, false));
@ -105,6 +224,9 @@ public class TestProfile extends PEGeneratedBase {
if (src.hasChild("status")) {
status = src.child("status").asDataType().primitiveValue();
}
if (src.hasChild("category")) {
category = ProfileObservationCategoryCode.fromCodeableConcept((CodeableConcept) src.child("category").asDataType());
}
if (src.hasChild("code")) {
code = (CodeableConcept) src.child("code").asDataType();
}
@ -174,7 +296,11 @@ public class TestProfile extends PEGeneratedBase {
}
tgt.clear("status");
if (status != null) {
tgt.makeChild("status").data().setProperty("value", new StringType(status));
tgt.makeChild("status").data().setProperty("value", new CodeType(status));
}
tgt.clear("category");
if (category != null) {
tgt.addChild("category", category.toCodeableConcept());
}
tgt.clear("code");
if (code != null) {
@ -203,6 +329,11 @@ public class TestProfile extends PEGeneratedBase {
}
private void initFixedValues() {
status = "final";
}
/**
* Test Observation Profile.
*
@ -309,13 +440,25 @@ public class TestProfile extends PEGeneratedBase {
return status;
}
public TestProfile setStatus(String value) {
this.status = value;
public boolean hasStatus() {
return true;
}
/**
* Test Observation Profile.
*
*/
public ProfileObservationCategoryCode getCategory() {
return category;
}
public TestProfile setCategory(ProfileObservationCategoryCode value) {
this.category = value;
return this;
}
public boolean hasStatus() {
return status != null;
public boolean hasCategory() {
return category != null;
}
/**
@ -438,6 +581,7 @@ public class TestProfile extends PEGeneratedBase {
complex = null;
identifier = null;
status = null;
category = null;
code = null;
subject = null;
encounter = null;

View File

@ -109,6 +109,12 @@ public class Utilities {
return inf.pluralize(word);
}
public static String singularise(String word) {
Inflector inf = new Inflector();
return inf.singularize(word);
}
public static boolean isInteger(String string) {
if (isBlank(string)) {
return false;
@ -556,6 +562,8 @@ public class Utilities {
return b.toString();
}
public static String unescapeJson(String json) throws FHIRException {
if (json == null)
return null;
@ -991,6 +999,23 @@ public class Utilities {
}
public static String escapeCSV(String value) {
if (value == null)
return "";
StringBuilder b = new StringBuilder();
for (char c : value.toCharArray()) {
if (c == '"')
b.append("\"\"");
else if (isWhitespace(c))
b.append(" ");
else
b.append(c);
}
return b.toString();
}
public static String escapeJson(String value) {
if (value == null)
return "";