diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileUtilities.java index 1cd948cd6..602852722 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileUtilities.java @@ -5268,5 +5268,31 @@ public class ProfileUtilities extends TranslatingUtilities { public void setDebug(boolean debug) { this.debug = debug; } + + + public static boolean isExtensionDefinition(StructureDefinition sd) { + return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension"); + } + + public static boolean isSimpleExtension(StructureDefinition sd) { + if (!isExtensionDefinition(sd)) { + return false; + } + ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); + return value != null && !value.isProhibited(); + } + + public static boolean isComplexExtension(StructureDefinition sd) { + if (!isExtensionDefinition(sd)) { + return false; + } + ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value"); + return value == null || value.isProhibited(); + } + + public static boolean isModifierExtension(StructureDefinition sd) { + ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension"); + return defn.getIsModifier(); + } } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java index 5d58c285b..31ffcf5aa 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java @@ -889,6 +889,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } + public T fetchResource(Class class_, String uri, String version) { + try { + return fetchResourceWithException(class_, uri+"|"+version); + } catch (FHIRException e) { + throw new Error(e); + } + } + @Override public boolean hasResource(Class class_, String uri) { try { @@ -1148,6 +1156,17 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); } + + public boolean isPrimitiveType(String type) { + return Utilities.existsInList(type, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "uuid", "xhtml", "url", "canonical"); + } + + public boolean isDataType(String type) { + return Utilities.existsInList(type, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", + "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext"); + } + + public boolean isTlogging() { return tlogging; } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/IWorkerContext.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/IWorkerContext.java index 90199645a..63bba4d35 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/IWorkerContext.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/IWorkerContext.java @@ -168,6 +168,7 @@ public interface IWorkerContext { * @throws Exception */ public T fetchResource(Class class_, String uri); + public T fetchResource(Class class_, String uri, String version); public T fetchResourceWithException(Class class_, String uri) throws FHIRException; @@ -488,4 +489,6 @@ public interface IWorkerContext { public void setUcumService(UcumService ucumService); public String getLinkForUrl(String corePath, String s); + + public boolean isPrimitiveType(String code); } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ElementDefinition.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ElementDefinition.java index 5023e7977..58b849ded 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ElementDefinition.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ElementDefinition.java @@ -9692,6 +9692,42 @@ public class ElementDefinition extends BackboneType implements ICompositeType { return hasFixed() ? getFixed() : getPattern(); } + public String getName() { + return hasPath() ? getPath().contains(".") ? getPath().substring(getPath().lastIndexOf(".")+1) : getPath() : null; + } + + public boolean getMustHaveValue() { + return false; + } + public boolean isChoice() { + return getPath().endsWith("[x]"); + } + + + public String getNameBase() { + return getName().replace("[x]", ""); + } + + public boolean unbounded() { + return getMax().equals("*") || Integer.parseInt(getMax()) > 1; + } + + public boolean isMandatory() { + return getMin() > 0; + } + + + public boolean prohibited() { + return "0".equals(getMax()); + } + + public boolean isProhibited() { + return "0".equals(getMax()); + } + + public boolean isRequired() { + return getMin() == 1; + } // end addition } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/StructureDefinition.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/StructureDefinition.java index 2aa1c382b..775d24d43 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/StructureDefinition.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/StructureDefinition.java @@ -1475,6 +1475,31 @@ public class StructureDefinition extends MetadataResource { } + public ElementDefinition getElementByPath(String path) { + if (path == null) { + return null; + } + for (ElementDefinition ed : getElement()) { + if (path.equals(ed.getPath()) || (path+"[x]").equals(ed.getPath())) { + return ed; + } + } + return null; + } + + + public ElementDefinition getElementById(String id) { + if (id == null) { + return null; + } + for (ElementDefinition ed : getElement()) { + if (id.equals(ed.getId())) { + return ed; + } + } + return null; + } + } @Block() @@ -4804,4 +4829,8 @@ public class StructureDefinition extends MetadataResource { public static final ca.uhn.fhir.model.api.Include INCLUDE_BASE = new ca.uhn.fhir.model.api.Include( "StructureDefinition:base").toLocked(); + public String getVersionedUrl() { + return hasVersion() ? getUrl()+"|"+getVersion() : getUrl(); + } + } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEBuilder.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEBuilder.java new file mode 100644 index 000000000..65a63505d --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEBuilder.java @@ -0,0 +1,613 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.r4.conformance.ProfileUtilities; +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType; +import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent; +import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; +import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; +import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ResourceFactory; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r4.utils.FHIRPathEngine; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.Utilities; + +/** + * Factory class for the ProfiledElement sub-system + * + * *** NOTE: This sub-system is still under development *** + * + * This subsystem takes a profile and creates a view of the profile that stitches + * all the parts together, and presents it as a seamless tree. There's two views: + * + * - definition: A logical view of the contents of the profile + * - instance: a logical view of a resource that conforms to the profile + * + * The tree of elements in the profile model is different to the the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Definition + * ---------- + * This presents a single view of the contents of a resource as specified by + * the profile. It's suitable for use in any kind of tree view. + * + * Each node has a unique name amongst it's siblings, but this name may not be + * the name in the instance, since slicing splits up a single named element into + * different definitions. + * + * Each node has: + * - name (unique amongst siblings) + * - schema name (the actual name in the instance) + * - min cardinality + * - max cardinality + * - short documentation (for the tree view) + * - full documentation (markdown source) + * - profile definition - the full definition in the profile + * - base definition - the full definition at the resource level + * - types() - a list of possible types + * - children(type) - a list of child nodes for the provided type + * - expansion - if there's a binding, the codes in the expansion based on the binding + * + * Note that the tree may not have leaves; the trees recurse indefinitely because + * extensions have extensions etc. So you can't do a depth-first search of the tree + * without some kind of decision to stop at a given point. + * + * Instance + * -------- + * + * todo + * + * @author grahamegrieve + * + */ +public class PEBuilder { + + public enum PEElementPropertiesPolicy { + NONE, EXTENSION, EXTENSION_ID + } + + private IWorkerContext context; + private ProfileUtilities pu; + private PEElementPropertiesPolicy elementProps; + private boolean fixedPropsDefault; + private FHIRPathEngine fpe; + + /** + * @param context - must be loaded with R5 definitions + * @param elementProps - whether to include Element.id and Element.extension in the tree. Recommended choice: Extension + */ + public PEBuilder(IWorkerContext context, PEElementPropertiesPolicy elementProps, boolean fixedPropsDefault) { + super(); + this.context = context; + this.elementProps = elementProps; + this.fixedPropsDefault = fixedPropsDefault; + pu = new ProfileUtilities(context, null, null); + fpe = new FHIRPathEngine(context, pu); + } + + /** + * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model + * for the provided version of the nominated profile + * + * The tree of elements in the profile model is different to those defined in the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Warning: profiles and resources are recursive; you can't iterate this tree until it you get + * to the leaves because there are nodes that don't terminate (extensions have extensions) + * + */ + public PEDefinition buildPEDefinition(StructureDefinition profile) { + if (!profile.hasSnapshot()) { + throw new DefinitionException("Profile '"+profile.getVersionedUrl()+"' does not have a snapshot"); + } + return new PEDefinitionResource(this, profile, profile.getName()); + } + + /** + * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model + * for the latest version of the nominated profile + * + * The tree of elements in the profile model is different to those defined in the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Warning: profiles and resources are recursive; you can't iterate this tree until it you get + * to the leaves because there are nodes that don't terminate (extensions have extensions) + * + */ + public PEDefinition buildPEDefinition(String url) { + StructureDefinition profile = getProfile(url); + if (profile == null) { + throw new DefinitionException("Unable to find profile for URL '"+url+"'"); + } + if (!profile.hasSnapshot()) { + throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); + } + return new PEDefinitionResource(this, profile, profile.getName()); + } + + /** + * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model + * for the nominated version of the nominated profile + * + * The tree of elements in the profile model is different to the the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get + * to the leaves because you will never get to a child that doesn't have children + * + */ + public PEDefinition buildPEDefinition(String url, String version) { + StructureDefinition profile = getProfile(url, version); + if (profile == null) { + throw new DefinitionException("Unable to find profile for URL '"+url+"'"); + } + if (!profile.hasSnapshot()) { + throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); + } + return new PEDefinitionResource(this, profile, profile.getName()); + } + + /** + * Given a resource and a profile, return a tree of instance data as defined by the profile model + * using the latest version of the profile + * + * The tree is a facade to the underlying resource - all actual data is stored against the resource, + * and retrieved on the fly from the resource, so that applications can work at either level, as + * convenient. + * + * Note that there's a risk that deleting something through the resource while holding + * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade + * that will continue to function, but is making changes to resource content that is no + * longer part of the resource + * + */ + public PEInstance buildPEInstance(String url, Resource resource) { + PEDefinition defn = buildPEDefinition(url); + return loadInstance(defn, resource); + } + + /** + * Given a resource and a profile, return a tree of instance data as defined by the profile model + * using the provided version of the profile + * + * The tree is a facade to the underlying resource - all actual data is stored against the resource, + * and retrieved on the fly from the resource, so that applications can work at either level, as + * convenient. + * + * Note that there's a risk that deleting something through the resource while holding + * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade + * that will continue to function, but is making changes to resource content that is no + * longer part of the resource + * + */ + public PEInstance buildPEInstance(StructureDefinition profile, Resource resource) { + PEDefinition defn = buildPEDefinition(profile); + return loadInstance(defn, resource); + } + + /** + * Given a resource and a profile, return a tree of instance data as defined by the profile model + * using the nominated version of the profile + * + * The tree is a facade to the underlying resource - all actual data is stored against the resource, + * and retrieved on the fly from the resource, so that applications can work at either level, as + * convenient. + * + * Note that there's a risk that deleting something through the resource while holding + * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade + * that will continue to function, but is making changes to resource content that is no + * longer part of the resource + */ + public PEInstance buildPEInstance(String url, String version, Resource resource) { + PEDefinition defn = buildPEDefinition(url, version); + return loadInstance(defn, resource); + } + + /** + * For the current version of a profile, construct a resource and fill out any fixed or required elements + * + * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created + * + * @param url identifies the profile + * @param version identifies the version of the profile + * @param meta whether to mark the profile in Resource.meta.profile + * @return constructed resource + */ + public Resource createResource(String url, String version, boolean meta) { + PEDefinition definition = buildPEDefinition(url, version); + Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); + populateByProfile(res, definition); + if (meta) { + res.getMeta().addProfile(definition.profile.getUrl()); + } + return res; + } + + /** + * For the provided version of a profile, construct a resource and fill out any fixed or required elements + * + * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created + * + * @param profile the profile + * @param meta whether to mark the profile in Resource.meta.profile + * @return constructed resource + */ + public Resource createResource(StructureDefinition profile, boolean meta) { + PEDefinition definition = buildPEDefinition(profile); + Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); + populateByProfile(res, definition); + if (meta) { + res.getMeta().addProfile(definition.profile.getUrl()); + } + return res; + } + + /** + * For the current version of a profile, construct a resource and fill out any fixed or required elements + * + * Note that fixed values are filled out irrespective of the value of fixedProps when the builder is created + * + * @param url identifies the profile + * @param meta whether to mark the profile in Resource.meta.profile + * @return constructed resource + */ + public Resource createResource(String url, boolean meta) { + PEDefinition definition = buildPEDefinition(url); + Resource res = ResourceFactory.createResource(definition.types().get(0).getType()); + populateByProfile(res, definition); + if (meta) { + res.getMeta().addProfile(definition.profile.getUrl()); + } + return res; + } + + + + // -- methods below here are only used internally to the package + + private StructureDefinition getProfile(String url) { + return context.fetchResource(StructureDefinition.class, url); + } + + + private StructureDefinition getProfile(String url, String version) { + return context.fetchResource(StructureDefinition.class, url, version); + } +// +// protected List listChildren(boolean allFixed, StructureDefinition profileStructure, ElementDefinition definition, TypeRefComponent t, CanonicalType u) { +// // TODO Auto-generated method stub +// return null; +// } + + protected List listChildren(boolean allFixed, PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition, String url, String... omitList) { + StructureDefinition profile = profileStructure; + List list = pu.getChildList(profile, definition); + if (definition.getType().size() == 1 || (!definition.getPath().contains(".")) || list.isEmpty()) { + assert url == null || checkType(definition, url); + List res = new ArrayList<>(); + if (list.size() == 0) { + profile = context.fetchResource(StructureDefinition.class, url); + list = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); + } + if (list.size() > 0) { + int i = 0; + while (i < list.size()) { + 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); + } + 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 { + i++; + } + } else { + i++; + } + } + } + return res; + } else if (list.isEmpty()) { + throw new DefinitionException("not done yet!"); + } else { + throw new DefinitionException("not done yet"); + } + } + + protected PEDefinition makeChild(PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition) { + PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, definition, parent.path()); + if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) { + pe.setMustHaveValue(definition.getMustHaveValue()); + } + pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue()); + return pe; + } + + private boolean passElementPropsCheck(ElementDefinition bdefn) { + switch (elementProps) { + case EXTENSION: + return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id"); + case NONE: + return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id", "Element.extension"); + case EXTENSION_ID: + default: + return true; + } + } + + private boolean isTypeSlicing(ElementDefinition defn) { + ElementDefinitionSlicingComponent sl = defn.getSlicing(); + return sl.getRules() == SlicingRules.CLOSED && sl.getDiscriminator().size() == 1 && + sl.getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE && "$this".equals(sl.getDiscriminatorFirstRep().getPath()); + } + + private boolean include(ElementDefinition defn) { + if (fixedPropsDefault) { + return true; + } else { + return !(defn.hasFixed() || defn.hasPattern()); + } + } + + protected List listSlices(StructureDefinition profileStructure, ElementDefinition definition, PEDefinition parent) { + List list = pu.getSliceList(profileStructure, definition); + List res = new ArrayList<>(); + for (ElementDefinition ed : list) { + if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) { + res.add(new PEDefinitionSubExtension(this, profileStructure, ed, parent.path())); + } else { + PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed, parent.path()); + pe.setRecursing(definition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension"))); + res.add(pe); + } + } + return res; + } + + + private boolean checkType(ElementDefinition defn, String url) { + for (TypeRefComponent t : defn.getType()) { + if (("http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode()).equals(url)) { + return true; + } + for (CanonicalType u : t.getProfile()) { + if (url.equals(u.getValue())) { + return true; + } + } + } + return !defn.getPath().contains("."); + } + + + private StructureDefinition getExtensionDefinition(ElementDefinition ed) { + if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) { + return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue()); + } else { + return null; + } + } + + + private ElementDefinition getByName(List blist, String name) { + for (ElementDefinition ed : blist) { + if (name.equals(ed.getName())) { + return ed; + } + } + return null; + } + + + protected PEType makeType(TypeRefComponent t) { + if (t.hasProfile()) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()); + if (sd == null) { + return new PEType(tail(t.getProfile().get(0).getValue()), t.getWorkingCode(), t.getProfile().get(0).getValue()); + } else { + return new PEType(sd.getName(), t.getWorkingCode(), t.getProfile().get(0).getValue()); + } + } else { + return makeType(t.getWorkingCode()); + } + } + + protected PEType makeType(TypeRefComponent t, CanonicalType u) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); + if (sd == null) { + return new PEType(tail(u.getValue()), t.getWorkingCode(), u.getValue()); + } else { + return new PEType(sd.getName(), t.getWorkingCode(), u.getValue()); + } + } + + + protected PEType makeType(String tn, String url) { + return new PEType(tn, tn, url); + } + + protected PEType makeType(String tn) { + return new PEType(tn, tn, "http://hl7.org/fhir/StructureDefinition/"+ tn); + } + + private String tail(String value) { + return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value; + } + + protected List getChildren(StructureDefinition profileStructure, ElementDefinition definition) { + return pu.getChildList(profileStructure, definition); + } + + private PEInstance loadInstance(PEDefinition defn, Resource resource) { + return new PEInstance(this, defn, resource, resource, defn.name()); + } + + public IWorkerContext getContext() { + return context; + } + + protected void populateByProfile(Base base, PEDefinition definition) { + if (definition.types().size() == 1) { + for (PEDefinition pe : definition.directChildren(true)) { + if (pe.fixedValue()) { + if (pe.definition().hasPattern()) { + base.setProperty(pe.schemaName(), pe.definition().getPattern()); + } else { + base.setProperty(pe.schemaName(), pe.definition().getFixed()); + } + } else if (!pe.isSlicer() && pe.max() == 1) { + for (int i = 0; i < pe.min(); i++) { + Base b = null; + if (pe.schemaName().endsWith("[x]")) { + if (pe.types().size() == 1) { + b = base.addChild(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType()))); + } + } else if (!pe.isBaseList()) { + b = base.makeProperty(pe.schemaName().hashCode(), pe.schemaName()); + } else { + b = base.addChild(pe.schemaName()); + } + if (b != null) { + populateByProfile(b, pe); + } + } + } + } + } + } + + public String makeSliceExpression(StructureDefinition profile, ElementDefinitionSlicingComponent slicing, ElementDefinition definition) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" and "); + for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) { + switch (d.getType()) { + case EXISTS: + throw new DefinitionException("The discriminator type 'exists' is not supported by the PEBuilder"); + case PATTERN: + throw new DefinitionException("The discriminator type 'pattern' is not supported by the PEBuilder"); + case PROFILE: + throw new DefinitionException("The discriminator type 'profile' is not supported by the PEBuilder"); + case TYPE: + throw new DefinitionException("The discriminator type 'type' is not supported by the PEBuilder"); + case VALUE: + String path = d.getPath(); + if (path.contains(".")) { + throw new DefinitionException("The discriminator path '"+path+"' is not supported by the PEBuilder"); + } + ElementDefinition ed = getChildElement(profile, definition, path); + if (ed == null) { + throw new DefinitionException("The discriminator path '"+path+"' could not be resolved by the PEBuilder"); + } + if (!ed.hasFixed()) { + throw new DefinitionException("The discriminator path '"+path+"' has no fixed value - this is not supported by the PEBuilder"); + } + if (!ed.getFixed().isPrimitive()) { + throw new DefinitionException("The discriminator path '"+path+"' has a fixed value that is not a primitive ("+ed.getFixed().fhirType()+") - this is not supported by the PEBuilder"); + } + b.append(path+" = '"+ed.getFixed().primitiveValue()+"'"); + break; + case NULL: + throw new DefinitionException("The discriminator type 'null' is not supported by the PEBuilder"); + default: + throw new DefinitionException("The discriminator type '??' is not supported by the PEBuilder"); + } + } + return b.toString(); + } + + private ElementDefinition getChildElement(StructureDefinition profile, ElementDefinition definition, String path) { + List elements = pu.getChildList(profile, definition); + if (elements.size() == 0) { + profile = definition.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, definition.getTypeFirstRep().getProfile().get(0).asStringValue()) : + context.fetchTypeDefinition(definition.getTypeFirstRep().getWorkingCode()); + elements = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); + } + return getByName(elements, path); + } + + public List exec(Resource resource, Base data, String fhirpath) { + return fpe.evaluate(this, resource, resource, data, fhirpath); + } + + public boolean isResource(String name) { + return context.getResourceNamesAsSet().contains(name); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinition.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinition.java new file mode 100644 index 000000000..572a10206 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinition.java @@ -0,0 +1,391 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.xmlbeans.impl.xb.xsdschema.All; +import org.hl7.fhir.exceptions.DefinitionException; +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.profilemodel.PEDefinition.PEDefinitionElementMode; +import org.hl7.fhir.utilities.Utilities; + +public abstract class PEDefinition { + + public enum PEDefinitionElementMode { + Resource, Element, DataType, Extension + } + + protected PEBuilder builder; + protected String name; + protected String path; + protected StructureDefinition profile; + protected ElementDefinition definition; + protected List types; + protected Map> children = new HashMap<>(); + private boolean recursing; + private boolean mustHaveValue; + private boolean inFixedValue; + private boolean isSlicer; + private List slices; // if there are some... + +// /** +// * Don't create one of these directly - always use the public methods on ProfiledElementBuilder +// * +// * @param builder +// * @param baseElement +// * @param profiledElement +// * @param data +// */ +// protected PEDefinition(PEBuilder builder, String name, +// ElementDefinition definition, Base data) { +// super(); +// this.builder = builder; +// this.name = name; +// this.definition = definition; +//// this.data = data; +// } + + protected PEDefinition(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, String ppath) { + this.builder = builder; + this.name = name; + this.profile = profile; + this.definition = definition; + this.path = path == null ? name : ppath+"."+name; + } + + + /** + * @return The name of the element or slice in the profile (always unique amongst children) + */ + public String name() { + return name; + } + + /** + * @return The path of the element or slice in the profile (name.name.name...) + */ + public String path() { + return path; + } + + /** + * @return The name of the element in the resource (may be different to the slice name) + */ + public String schemaName() { + String n = definition.getName(); + return n; + } + + /** + * @return The name of the element in the resource (may be different to the slice name) + */ + public String schemaNameWithType() { + String n = definition.getName(); + if (n.endsWith("[x]") && types().size() == 1) { + n = n.replace("[x]", Utilities.capitalize(types.get(0).getType())); + } + return n; + } + + /** + * @return a list of types. There is usually at least one type; it might be Element, Type, BackboneElement or BackboneType + * + * The following elements don't have types (true primitives): Element.id. Extension.url, PrimitiveType.value + */ + public List types() { + if (types == null) { + List ltypes = new ArrayList<>(); + listTypes(ltypes); + types = ltypes; + } + return types; + } + + protected abstract void listTypes(List types); + + /** + * @return The minimum number of repeats allowed + */ + public int min() { + return mustHaveValue ? 1 : definition.getMin(); + } + + /** + * @return the maximum number of repeats allowed + */ + public int max() { + return definition.getMax() == null || "*".equals(definition.getMax()) ? Integer.MAX_VALUE : Integer.parseInt(definition.getMax()); + } + + /** + * @return the definition of the element in the profile (fully populated) + * + * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled + */ + public ElementDefinition definition() { + return definition; + } + + /** + * @return the definition of the element in the base specification + * + * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled + */ + public ElementDefinition baseDefinition() { + String type = definition.getBase().getPath(); + if (type.contains(".")) { + type= type.substring(0, type.indexOf(".")); + } + StructureDefinition sd = builder.getContext().fetchTypeDefinition(type); + return sd.getSnapshot().getElementByPath(definition.getBase().getPath()); + } + + /** + * @return the short documentation of the definition (shown in the profile table view) + */ + public String shortDocumentation() { + return definition.getShort(); + } + + /** + * @return the full definition of the element (markdown syntax) + */ + public String documentation() { + return definition.getDefinition(); + } + +// /** +// * @return if the profiled definition has a value set, get the expansion +// */ +// public ValueSet expansion() { +// throw new NotImplementedException("Not done yet"); +// } +// + /** + * @param typeUrl - the url of one of the types listed in types() + * @return - the list of children for the nominated type + * + * Warning: profiles and resources can be recursive; you can't iterate this tree until you get + * to the leaves because you will never get to a child that doesn't have children (extensions have extensions etc) + * + */ + public List children(String typeUrl) { + return children(typeUrl, false); + } + + public List children(String typeUrl, boolean allFixed) { + if (children.containsKey(typeUrl+"$"+allFixed)) { + return children.get(typeUrl+"$"+allFixed); + } + List res = new ArrayList<>(); + makeChildren(typeUrl, res, allFixed); + children.put(typeUrl+"$"+allFixed, res); + return res; + } + + public List children() { + 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()+")"); + } + } + + public List children(boolean allFixed) { + 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()+")"); + } + } + + /** + * @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() { + return definition.hasFixed() || definition.hasPattern(); + } + + protected abstract void makeChildren(String typeUrl, List children, boolean allFixed); + + @Override + public String toString() { + return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\""; + } + + /** + * @return true if the builder observes that this element is recursing (extensions have extensions) + * + * Note that this is unreliable and may be withdrawn if it can't be fixed + */ + public boolean isRecursing() { + return recursing; + } + + protected void setRecursing(boolean recursing) { + this.recursing = recursing; + } + + protected boolean isMustHaveValue() { + return mustHaveValue; + } + + protected void setMustHaveValue(boolean mustHaveValue) { + this.mustHaveValue = mustHaveValue; + } + + /** + * @return true if this property is inside an element that has an assigned fixed value + */ + public boolean isInFixedValue() { + return inFixedValue; + } + + + protected void setInFixedValue(boolean inFixedValue) { + this.inFixedValue = inFixedValue; + } + + + /** + * This is public to support unit testing - there's no reason to use it otherwise + * + * @return used in the instance processor to differentiate slices + */ + public abstract String fhirpath(); + + + public boolean isList() { + return "*".equals(definition.getMax()); + } + + + public boolean repeats() { + return max() > 1; + } + + public PEDefinitionElementMode mode() { + if (builder.isResource(definition.getBase().getPath())) { + return PEDefinitionElementMode.Resource; + } + for (TypeRefComponent tr : definition.getType()) { + if ("Extension".equals(tr.getWorkingCode())) { + return PEDefinitionElementMode.Extension; + } + if (!Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) { + return PEDefinitionElementMode.DataType; + } + } + return PEDefinitionElementMode.Element; + } + + /** + * @return true if this element is profiled one way or another + */ + public boolean isProfiled() { + return !profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition"); + } + + + public boolean isSlicer() { + return isSlicer; + } + + + public void setSlicer(boolean isSlicer) { + this.isSlicer = isSlicer; + } + + + public boolean isBaseList() { + return !"1".equals(definition.getBase().getMax()); + } + + + public StructureDefinition getProfile() { + return profile; + } + + + public boolean isKeyElement() { + boolean selfKey = definition.getMustSupport() || definition.getMustHaveValue() || min() > 0 || definition.hasCondition(); + if (isProfiled() && !selfKey) { + if (types() != null && types().size() > 0) { + for (PEDefinition child : children()) { + if (child.isKeyElement()) { + return true; + } + } + } + } + return selfKey; + } + + + public boolean isPrimitive() { + return types().size() == 1 && builder.getContext().isPrimitiveType(types.get(0).getName()); + } + + + public boolean isBasePrimitive() { + ElementDefinition ed = baseDefinition(); + return ed != null && ed.getType().size() == 1 && builder.getContext().isPrimitiveType(ed.getType().get(0).getWorkingCode()); + } + + + // extensions do something different here + public List directChildren(boolean allFixed) { + return children(allFixed); + } + + + public List getSlices() { + return slices; + } + + + public void setSlices(List slices) { + this.slices = slices; + } + + + public boolean isExtension() { + return false; + } + +} + + diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionElement.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionElement.java new file mode 100644 index 000000000..766cd44a3 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionElement.java @@ -0,0 +1,84 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.List; + +import org.hl7.fhir.r4.model.CanonicalType; +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.utilities.CommaSeparatedStringBuilder; + +public class PEDefinitionElement extends PEDefinition { + + public PEDefinitionElement(PEBuilder builder, StructureDefinition profile, ElementDefinition definition, String ppath) { + super(builder, definition.getName(), profile, definition, ppath); + } + + @Override + public void listTypes(List types) { + for (TypeRefComponent t : definition.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else if (!t.getCode().startsWith("http://hl7.org/fhirpath/")) { + types.add(new PEType(t.getWorkingCode(), t.getWorkingCode(), "http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode())); + } + } + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + children.addAll(builder.listChildren(allFixed, this, profile, definition, typeUrl)); + } + + @Override + public String fhirpath() { + String base = definition.getName().replace("[x]", ""); + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or "); + if (getSlices() != null) { + for (PEDefinition slice : getSlices()) { + b.append("("+builder.makeSliceExpression(slice.profile, definition.getSlicing(), slice.definition())+")"); + } + } else if (definition.hasSlicing()) { + List slices = builder.listSlices(profile, definition, this); + for (PEDefinition slice : slices) { + b.append("("+builder.makeSliceExpression(profile, definition.getSlicing(), slice.definition())+")"); + } + } + if (b.count() == 0) + return base; + else + return base+".where(("+b.toString()+").not())"; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionExtension.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionExtension.java new file mode 100644 index 000000000..77f94c766 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionExtension.java @@ -0,0 +1,118 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.r4.conformance.ProfileUtilities; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; +import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r4.profilemodel.PEDefinition.PEDefinitionElementMode; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.r4.model.StructureDefinition; + +public class PEDefinitionExtension extends PEDefinition { + + private StructureDefinition extension; + private ElementDefinition sliceDefinition; + private ElementDefinition eed; + private ElementDefinition ved; + + public PEDefinitionExtension(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition, StructureDefinition extension, String ppath) { + super(builder, name, profile, definition, ppath); + this.sliceDefinition = sliceDefinition; + this.extension= extension; + eed = extension.getSnapshot().getElementByPath("Extension.extension"); + ved = extension.getSnapshot().getElementByPath("Extension.value[x]"); + } + + @Override + public void listTypes(List types) { + if (ved.isRequired() || eed.isProhibited()) { + for (TypeRefComponent t : ved.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else { + types.add(builder.makeType(t.getWorkingCode())); + } + } + } else if (ProfileUtilities.isComplexExtension(extension)) { + types.add(builder.makeType(extension.getName(), extension.getUrl())); + } else { + types.add(builder.makeType("Extension")); + } + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + if (ved.isRequired() || eed.isProhibited()) { + children.addAll(builder.listChildren(allFixed, this, extension, ved, typeUrl)); + } else { + List slices = builder.listSlices(extension, eed, this); + if (eed.getSlicing().getRules() != SlicingRules.CLOSED) { + children.addAll(builder.listChildren(allFixed, this, extension, eed, "http://hl7.org/fhir/StructureDefinition/Extension", "value[x]", "url")); + if (!children.isEmpty()) { + children.get(0).setSlices(slices); + } + } + children.addAll(slices); + } + } + + @Override + public String fhirpath() { + if (ved.isRequired() || eed.isProhibited()) { + return "extension('"+extension.getUrl()+"').value"; + } else { + return "extension('"+extension.getUrl()+"')"; + } + } + + public PEDefinitionElementMode mode() { + return PEDefinitionElementMode.Extension; + } + + public List directChildren(boolean allFixed) { + List children = new ArrayList<>(); + children.addAll(builder.listChildren(allFixed, this, extension, extension.getSnapshot().getElementFirstRep(), "http://hl7.org/fhir/StructureDefinition/Extension")); + return children; + } + + + public boolean isExtension() { + return true; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionResource.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionResource.java new file mode 100644 index 000000000..f63875d04 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionResource.java @@ -0,0 +1,57 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.List; + +import org.hl7.fhir.r4.model.StructureDefinition; + +public class PEDefinitionResource extends PEDefinition { + + public PEDefinitionResource(PEBuilder builder, StructureDefinition profile, String ppath) { + super(builder, profile.getName(), profile, profile.getSnapshot().getElementFirstRep(), ppath); + } + + @Override + public void listTypes(List types) { + types.add(new PEType(profile.getName(), profile.getType(), profile.getUrl())); + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + children.addAll(builder.listChildren(allFixed, this, profile, definition, null)); + } + + @Override + public String fhirpath() { + return profile.getType(); + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSlice.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSlice.java new file mode 100644 index 000000000..b0cff9192 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSlice.java @@ -0,0 +1,72 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.List; + +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; + +public class PEDefinitionSlice extends PEDefinition { + + protected ElementDefinition sliceDefinition; + + public PEDefinitionSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition profileDefinition, ElementDefinition sliceDefinition, String ppath) { + super(builder, name, profile, profileDefinition, ppath); + this.sliceDefinition = sliceDefinition; + } + + @Override + public void listTypes(List types) { + for (TypeRefComponent t : definition.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else if (!t.getCode().startsWith("http://hl7.org/fhirpath/")) { + types.add(new PEType(t.getWorkingCode(), t.getWorkingCode(), "http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode())); + } + } + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + children.addAll(builder.listChildren(allFixed, this, profile, definition, typeUrl)); + } + + @Override + public String fhirpath() { + String base = schemaName().replace("[x]", ""); + String filter = builder.makeSliceExpression(profile, sliceDefinition.getSlicing(), definition); + return base+".where("+filter+")"; + } + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSubExtension.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSubExtension.java new file mode 100644 index 000000000..792658eda --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionSubExtension.java @@ -0,0 +1,117 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules; +import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r4.profilemodel.PEDefinition.PEDefinitionElementMode; +import org.hl7.fhir.r4.model.StructureDefinition; + +public class PEDefinitionSubExtension extends PEDefinition { + + private ElementDefinition eed; + private ElementDefinition ved; + private ElementDefinition ued; + + public PEDefinitionSubExtension(PEBuilder builder, StructureDefinition profile, ElementDefinition definition, String ppath) { + super(builder, definition.getSliceName(), profile, definition, ppath); + List childDefs = builder.getChildren(profile, definition); + eed = getElementByName(childDefs, "extension"); + ved = getElementByName(childDefs, "value[x]"); + ued = getElementByName(childDefs, "url"); + } + + @Override + public void listTypes(List types) { + if (ved.isRequired() || eed.isProhibited()) { + for (TypeRefComponent t : ved.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else { + types.add(builder.makeType(t.getWorkingCode())); + } + } + } else { + types.add(builder.makeType("Extension")); + } + } + + private ElementDefinition getElementByName(List children, String name) { + for (ElementDefinition ed : children) { + if (name.equals(ed.getName())) { + return ed; + } + } + return null; + } + + public List directChildren(boolean allFixed) { + List children = new ArrayList<>(); + children.addAll(builder.listChildren(allFixed, this, profile, definition, "http://hl7.org/fhir/StructureDefinition/Extension")); + return children; + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + if (ved.isRequired() || eed.isProhibited()) { + children.addAll(builder.listChildren(allFixed, this, profile, ved, typeUrl)); + } else { + if (eed.getSlicing().getRules() != SlicingRules.CLOSED) { + children.addAll(builder.listChildren(allFixed, this, profile, eed, "http://hl7.org/fhir/StructureDefinition/Extension", "value[x]", "url")); + } + children.addAll(builder.listSlices(profile, eed, this)); + } + } + + @Override + public String fhirpath() { + if (ved.isRequired() || eed.isProhibited()) { + return "extension('"+ued.getFixed().primitiveValue()+"').value"; + } else { + return "extension('"+ued.getFixed().primitiveValue()+"')"; + } + } + + public PEDefinitionElementMode mode() { + return PEDefinitionElementMode.Extension; + } + + public boolean isExtension() { + return true; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionTypeSlice.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionTypeSlice.java new file mode 100644 index 000000000..2ace0299d --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEDefinitionTypeSlice.java @@ -0,0 +1,76 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.List; + +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; + +public class PEDefinitionTypeSlice extends PEDefinition { + + protected ElementDefinition sliceDefinition; + + public PEDefinitionTypeSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition, String ppath) { + super(builder, name, profile, definition, ppath); + this.sliceDefinition = sliceDefinition; + } + + @Override + public void listTypes(List types) { + for (TypeRefComponent t : definition.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else if (!t.getCode().startsWith("http://hl7.org/fhirpath/")) { + types.add(new PEType(t.getWorkingCode(), t.getWorkingCode(), "http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode())); + } + } + } + + @Override + protected void makeChildren(String typeUrl, List children, boolean allFixed) { + children.addAll(builder.listChildren(allFixed, this, profile, definition, typeUrl)); + } + + @Override + public String fhirpath() { + String base = definition.getName().replace("[x]", ""); + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" | "); + for (TypeRefComponent t : definition.getType()) { + b.append(base+".ofType("+t.getWorkingCode()+")"); + } + return b.toString(); + } + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEInstance.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEInstance.java new file mode 100644 index 000000000..67c306062 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEInstance.java @@ -0,0 +1,310 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.BaseDateTimeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ContactPoint; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Type; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.model.Address; +import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; + +/** + * This class provides a profile centric view of a resource, as driven by a profile + * + * This class is also suitable to be used as the base of a POJO + * @author grahamegrieve + * + */ +public class PEInstance { + + private PEBuilder builder; + private PEDefinition definition; + private Resource resource; // for FHIRPath + private Base data; + private String path; + + protected PEInstance(PEBuilder builder, PEDefinition definition, Resource resource, Base data, String path) { + super(); + this.builder = builder; + this.definition = definition; + this.resource = resource; + this.data = data; + this.path = path; + } + + /** + * @return definition information about this instance data + */ + public PEDefinition definition() { + return definition; + } + + /** + * @return the type of this element + */ + public PEType type() { + return definition.types().get(0); + } + + /** + * @return all the children of this instance data + */ + public List children() { + List res = new ArrayList<>(); + for (PEDefinition child : definition.children()) { + List instances = builder.exec(resource, data, child.fhirpath()); + int i = 0; + for (Base b : instances) { + res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": ""))); + i++; + } + } + return res; + } + + /** + * @return all the single children of this instance data for the named property. An exception if there's more than one, null if there's none + */ + public PEInstance child(String name) { + PEDefinition child = byName(definition.children(), name); + List instances = builder.exec(resource, data, child.fhirpath()); + if (instances.isEmpty()) { + return null; + } else if (instances.size() == 1) { + return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": "")); + } else { + throw new FHIRException("Found multiple instances for "+name+"@ "+path); + } + } + + /** + * @return all the children of this instance data for the named property + */ + public List children(String name) { + PEDefinition child = byName(definition.children(), name); + List res = new ArrayList<>(); + List instances = builder.exec(resource, data, child.fhirpath()); + int i = 0; + for (Base b : instances) { + res.add(new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.repeats() ? "["+i+"]": ""))); + i++; + } + return res; + } + + private PEDefinition byName(List children, String name) { + for (PEDefinition defn : children) { + if (defn.name().equals(name)) { + return defn; + } + if (defn.name().equals(name+"[x]")) { + return defn; + } + } + throw new FHIRException("No children with the name '"+name+"'"); + } + + /** + * @return make a child, and append it to existing children (if they exist) + */ + public PEInstance makeChild(String name) { + PEDefinition child = byName(definition.children(), name); + Base b = child.isBaseList() || !child.isBasePrimitive() ? data.addChild(child.schemaNameWithType()) : data.makeProperty(child.schemaNameWithType().hashCode(), child.schemaNameWithType()); + builder.populateByProfile(b, child); + return new PEInstance(builder, child, resource, b, path+"."+child.name()); + } + + /** + * @return get a child. if it doesn't exist, make one + */ + public PEInstance forceChild(String name) { + PEDefinition child = byName(definition.children(), name); + List instances = builder.exec(resource, data, child.fhirpath()); + if (instances.isEmpty()) { + Base b = data.addChild(child.schemaName()); + builder.populateByProfile(b, child); + return new PEInstance(builder, child, resource, b, path+"."+child.name()+(child.isList() ? "[0]": "")); + } else { + return new PEInstance(builder, child, resource, instances.get(0), path+"."+child.name()+(child.repeats() ? "[0]": "")); + } + } + + /** + * remove the nominated child from the resource + */ + public void removeChild(PEInstance child) { + data.removeChild(child.definition().schemaName(), child.data); + } + + public void clear(String name) { + List children = children(name); + for (PEInstance child : children) { + removeChild(child); + } + } + + public enum PEInstanceDataKind { + Resource, Complex, DataType, Primitive + } + + /** + * @return the kind of data behind this profiled node + */ + public PEInstanceDataKind getDataKind() { + if (data instanceof Resource) { + return PEInstanceDataKind.Resource; + } + if (data instanceof PrimitiveType) { + return PEInstanceDataKind.Primitive; + } + if (data instanceof Type) { + return PEInstanceDataKind.DataType; + } + return PEInstanceDataKind.Complex; + } + + public Base data() { + return data; + } + + /** + * @return if dataKind = Resource, get the underlying resource, otherwise an exception + */ + public Resource asResource() { + return (Resource) data; + } + + /** + * @return if dataKind = Datatype, get the underlying resource, otherwise an exception + */ + public Type asDataType() { + return (Type) data; + } + + public CodeableConcept asCodeableConcept() { + return (CodeableConcept) asDataType(); + } + + public Identifier Identifier() { + return (Identifier) asDataType(); + } + + public Quantity asQuantity() { + return (Quantity) asDataType(); + } + + public HumanName asHumanName() { + return (HumanName) asDataType(); + } + + public Address Address() { + return (Address) asDataType(); + } + + public ContactPoint asContactPoint() { + return (ContactPoint) asDataType(); + } + + public Reference asReference() { + return (Reference) asDataType(); + } + + + /** + * @return if dataKind = PrimitiveValue, get the underlying resource, otherwise an exception + * + * Note that this is for e.g. String.value, not String itself + */ + public String getPrimitiveAsString() { + return data.primitiveValue(); + } + + public Date getPrimitiveAsDate() { + if (data instanceof BaseDateTimeType) { + return ((DateTimeType) data).getValue(); + } + return null; + } + + public void setPrimitiveValue(String value) { + PrimitiveType pt = (PrimitiveType) data; + pt.setValueAsString(value); + } + + public String getPath() { + return path; + } + + public Base getBase() { + return data; + } + + public boolean hasChild(String name) { + PEDefinition child = byName(definition.children(), name); + List instances = builder.exec(resource, data, child.fhirpath()); + return !instances.isEmpty(); + } + + public IWorkerContext getContext() { + return builder.getContext(); + } + + public void addChild(String name, Type value) { + PEDefinition child = byName(definition.children(), name); + Base b = data.setProperty(child.schemaName(), value); + } + + public void addChild(String name, String value) { + PEDefinition child = byName(definition.children(), name); + Base b = data.setProperty(child.schemaName(), new StringType(value)); + } + + public void addChild(String name, Date value) { + PEDefinition child = byName(definition.children(), name); + Base b = data.setProperty(child.schemaName(), new DateType(value)); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEType.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEType.java new file mode 100644 index 000000000..5e532de05 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/PEType.java @@ -0,0 +1,73 @@ +package org.hl7.fhir.r4.profilemodel; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +public class PEType { + private String name; + private String type; + private String url; + public PEType(String name, String type, String url) { + super(); + this.name = name; + this.url = url; + this.type = type; + } + + /** + * Human presentable name + * + * @return + */ + public String getName() { + return name; + } + + /** + * The concrete FHIR type name for the type + * + * @return + */ + public String getType() { + return type; + } + + /** + * URL that identifies the type + * @return + */ + public String getUrl() { + return url; + } + + @Override + public String toString() { + return (name.equals(type) ? name : name+"->"+type)+"["+url+"]"; + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PECodeGenerator.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PECodeGenerator.java new file mode 100644 index 000000000..4ba15d961 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PECodeGenerator.java @@ -0,0 +1,650 @@ +package org.hl7.fhir.r4.profilemodel.gen; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.ArrayList; +import java.util.Date; + +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.model.CodeableConcept; +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.profilemodel.PEBuilder; +import org.hl7.fhir.r4.profilemodel.PEBuilder.PEElementPropertiesPolicy; +import org.hl7.fhir.r4.profilemodel.gen.PECodeGenerator.ExtensionPolicy; +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.TextFile; +import org.hl7.fhir.utilities.Utilities; + + +public class PECodeGenerator { + + + public static final String DEFAULT_DATE() { + SimpleDateFormat sdf = new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + + public enum ExtensionPolicy { + None, Complexes, Primitives; + } + + private class PEGenClass { + private String name; + private String base; + private String doco; + private String url; + private boolean isResource; + private StringBuilder fields = new StringBuilder(); + private StringBuilder load = new StringBuilder(); + private StringBuilder save = new StringBuilder(); + private StringBuilder clear = new StringBuilder(); + private StringBuilder copy = new StringBuilder(); + private StringBuilder accessors = new StringBuilder(); + 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); + genClear(false, "id"); + } + } + public void write(StringBuilder b, String copyright) { + w(b); + if (copyright != null) { + w(b, "/*"); + w(b, copyright); + w(b, " */"); + w(b); + } + w(b, "// Generated by the HAPI Java Profile Generator, "+genDate); + w(b); + jdoc(b, doco, 0, true); + w(b, "public class "+name+" extends PEGeneratedBase {"); + w(b); + if (url != null) { + w(b, " private static final String CANONICAL_URL = \""+url+"\";"); + w(b); + } + 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); + w(b, " public "+name+"() {"); + w(b, " // todo"); + 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) {"); + 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));"); + w(b, " load(src);"); + w(b, " }"); + w(b); + jdoc(b, "Populate an instance of the object based on this source object ", 2, true); + w(b, " public static "+name+" fromSource(IWorkerContext context, "+base+" source) {"); + w(b, " "+name+" theThing = new "+name+"();"); + w(b, " theThing.workerContext = context;"); + w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); + w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, source);"); + w(b, " theThing.load(src);"); + w(b, " return theThing;"); + w(b, " }"); + w(b); + } else { + jdoc(b, "Used when loading other models ", 2, true); + w(b, " public static "+name+" fromSource(PEInstance source) {"); + w(b, " "+name+" theThing = new "+name+"();"); + w(b, " theThing.workerContext = source.getContext();"); + w(b, " theThing.load(source);"); + w(b, " return theThing;"); + w(b, " }"); + } + w(b); + w(b, " public void load(PEInstance src) {"); + w(b, " clear();"); + w(b, load.toString()); + w(b, " }"); + w(b); + + if (isResource) { + jdoc(b, "Build an instance of the object based on this source object ", 2, true); + w(b, " public "+base+" build(IWorkerContext context) {"); + w(b, " workerContext = context;"); + w(b, " "+base+" theThing = new "+base+"();"); + w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); + w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, theThing);"); + w(b, " save(tgt, false);"); + w(b, " return theThing;"); + w(b, " }"); + w(b); + jdoc(b, "Save this profile class into an existing resource (overwriting enything that exists in the profile) ", 2, true); + w(b, " public void save(IWorkerContext context, "+base+" dest, boolean nulls) {"); + w(b, " workerContext = context;"); + w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);"); + w(b, " PEInstance tgt = builder.buildPEInstance(CANONICAL_URL, dest);"); + w(b, " save(tgt, nulls);"); + w(b, " }"); + w(b); + } + w(b, " public void save(PEInstance tgt, boolean nulls) {"); + w(b, save.toString()); + w(b, " }"); + w(b); + w(b, accessors.toString()); + w(b); + w(b, " public void clear() {"); + w(b, clear.toString()); + w(b, " }"); + w(b); + w(b, "}"); + } + + private void defineField(PEDefinition source, PEDefinition field) { + if (field.types().size() == 1) { + StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl()); + if (sd != null) { + boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; + boolean isAbstract = sd.getAbstract(); + String name = field.name().replace("[x]", ""); + String sname = name; + String type = null; + String init = ""; + String ptype = type; + if (isPrim) { + // todo: are we extension-less? + type = Utilities.capitalize(field.types().get(0).getName()+"Type"); + ptype = getPrimitiveType(sd); + } else { + type = field.types().get(0).getName(); + } + String ltype = type; + if (field.isList()) { + ltype = "List<"+type+">"; + init = "new ArrayList<>()"; + if (!Utilities.existsInList(name, "contained")) { + name = Utilities.pluralize(name, 2); + } + } + String cname = Utilities.capitalize(name); + 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)); + genClear(field.isList(), name); + } + } else { + // ignoring polymorphics for now + } + + } + + private void genClear(boolean list, String name) { + if (list) { + w(clear, " "+name+".clear();"); + } else { + w(clear, " "+name+" = null;"); + } + } + + 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) { + if (isList) { + w(load, " for (PEInstance item : src.children(\""+sname+"\")) {"); + w(load, " "+name+".add(("+type+") item.asDataType());"); + w(load, " }"); + } else if (isPrim) { + w(load, " if (src.hasChild(\""+name+"\")) {"); + if ("CodeType".equals(type)) { + // might be code or enum + w(load, " "+name+" = src.child(\""+name+"\").asDataType().primitiveValue();"); + } else { + w(load, " "+name+" = (("+type+") src.child(\""+name+"\").asDataType()).getValue();"); + } + w(load, " }"); + } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { + w(load, " if (src.hasChild(\""+name+"\")) {"); + w(load, " "+name+" = "+type+".fromSource(src.child(\""+name+"\"));"); + w(load, " }"); + } else { + w(load, " if (src.hasChild(\""+name+"\")) {"); + w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();"); + w(load, " }"); + } + } + + 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) { + w(save, " tgt.clear(\""+sname+"\");"); + if (isList) { + w(save, " for ("+type+" item : "+name+") {"); + if (isExtension) { + w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", item);"); + } else { + w(save, " tgt.addChild(\""+sname+"\", item);"); + } + w(save, " }"); + } else if (isPrim) { + w(save, " if ("+name+" != null) {"); + if (isExtension) { + w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", new "+type+"("+name+"));"); + } else if (Utilities.existsInList(type, "DateType", "InstantType", "DateTimeType")) { + w(save, " tgt.addChild(\""+sname+"\", new "+type+"("+name+"));"); + } else { + w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value\", new "+type+"("+name+"));"); + } + w(save, " }"); + } else if (typeInfo != null && typeInfo.getUrl() != null && !typeInfo.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition")) { + w(save, " if ("+name+" != null) {"); + w(save, " "+name+".save(tgt.makeChild(\""+sname+"\"), nulls);"); + w(save, " }"); + } else if (isExtension) { + w(save, " if ("+name+" != null) {"); + w(save, " tgt.makeChild(\""+sname+"\").data().setProperty(\"value[x]\", "+name+");"); + w(save, " }"); + } else { + w(save, " if ("+name+" != null) {"); + w(save, " tgt.addChild(\""+sname+"\", "+name+");"); + w(save, " }"); + } + } + + 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) { + jdoc(accessors, doco, 2, true); + if (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, " }"); + } else { + if (isPrim && !isList) { + w(accessors, " public "+ptype+" get"+cname+"() {"); + w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); + w(accessors, " return "+name+".getValue();"); + w(accessors, " }"); + w(accessors, " public "+ltype+" get"+cname+"Element() {"); + } else if (isAbstract && !isList) { + w(accessors, " public @Nullable "+ltype+" get"+cname+"() { // "+ltype+" is abstract "); + } else { + w(accessors, " public "+ltype+" get"+cname+"() {"); + } + if (isList) { + w(accessors, " if ("+name+" == null) { "+name+" = "+init+"; }"); + } else if (!isAbstract) { + w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); + } + w(accessors, " return "+name+";"); + w(accessors, " }"); + w(accessors); + if (isList) { + w(accessors, " public boolean has"+cname+"() {"); + w(accessors, " return "+name+" != null && !"+name+".isEmpty();"); + w(accessors, " }"); + w(accessors); + if (!isAbstract) { + w(accessors, " public "+type+" add"+csname+"() {"); + w(accessors, " "+type+" theThing = new "+type+"();"); + w(accessors, " get"+cname+"().add(theThing);"); + w(accessors, " return theThing;"); + w(accessors, " }"); + w(accessors); + } + w(accessors, " public boolean has"+csname+"("+type+" item) {"); + w(accessors, " return has"+cname+"() && "+name+".contains(item);"); + w(accessors, " }"); + w(accessors); + w(accessors, " public void remove"+csname+"("+type+" item) {"); + w(accessors, " if (has"+csname+"(item)) {"); + w(accessors, " "+name+".remove(item);"); + w(accessors, " }"); + w(accessors, " }"); + w(accessors); + } else if (isPrim) { + if (!isFixed) { + w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {"); + w(accessors, " if ("+name+" == null) { "+name+" = new "+type+"(); }"); + w(accessors, " "+name+".setValue(value);"); + w(accessors, " return this;"); + w(accessors, " }"); + w(accessors, " public "+this.name+" set"+cname+"Element("+type+" value) {"); + w(accessors, " this."+name+" = value;"); + w(accessors, " return this;"); + w(accessors, " }"); + } + w(accessors, " public boolean has"+cname+"() {"); + w(accessors, " return "+name+" != null && "+name+".hasValue();"); + w(accessors, " }"); + w(accessors); + } else { + if (!isFixed) { + w(accessors, " public "+this.name+" set"+cname+"("+type+" value) {"); + w(accessors, " this."+name+" = value;"); + w(accessors, " return this;"); + w(accessors, " }"); + } + w(accessors, " public boolean has"+cname+"() {"); + w(accessors, " return "+name+" != null;"); + w(accessors, " }"); + } + } + 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); + if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) { + w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco); + } else if (isList) { + w(fields, " private "+ltype+" "+name+" = new ArrayList<>();"+nn+" // "+shortDoco); + } else { + w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco); + } + } + } + + private String folder; + private IWorkerContext workerContext; + private String canonical; + private String pkgName; + + // options: + private ExtensionPolicy extensionPolicy; + private boolean narrative; + private boolean contained; + private boolean meta; + private String language; + private boolean keyElementsOnly; + private String genDate = DEFAULT_DATE(); + + + public PECodeGenerator(IWorkerContext workerContext) { + super(); + this.workerContext = workerContext; + } + + public String getFolder() { + return folder; + } + + + public void setFolder(String folder) { + this.folder = folder; + } + + + public String getCanonical() { + return canonical; + } + + public void setCanonical(String canonical) { + this.canonical = canonical; + } + + + public String getPkgName() { + return pkgName; + } + + public void setPkgName(String pkgName) { + this.pkgName = pkgName; + } + + public ExtensionPolicy getExtensionPolicy() { + return extensionPolicy; + } + + public void setExtensionPolicy(ExtensionPolicy extensionPolicy) { + this.extensionPolicy = extensionPolicy; + } + + public boolean isNarrative() { + return narrative; + } + + public void setNarrative(boolean narrative) { + this.narrative = narrative; + } + + public boolean isMeta() { + return meta; + } + + public void setMeta(boolean meta) { + this.meta = meta; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public boolean isKeyElementsOnly() { + return keyElementsOnly; + } + + public void setKeyElementsOnly(boolean keyElementsOnly) { + this.keyElementsOnly = keyElementsOnly; + } + + public boolean isContained() { + return contained; + } + + public void setContained(boolean contained) { + this.contained = contained; + } + + public String getGenDate() { + return genDate; + } + + public void setGenDate(String genDate) { + this.genDate = genDate; + } + + private StringBuilder imports = new StringBuilder(); + + /** + * @throws IOException + * + */ + public void execute() throws IOException { + 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;"); + PEGenClass cls = genClass(source); + StringBuilder b = new StringBuilder(); + w(b, "package "+pkgName+";"); + w(b); + if (source.getProfile().hasCopyright()) { + jdoc(b, source.getProfile().getCopyright(), 0, false); + } + w(b, imports.toString()); + cls.write(b, source.getProfile().getCopyright()); + TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java")); + } + + public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) { + if (!Utilities.noString(doco)) { + String pfx = Utilities.padLeft("", ' ', indent); + w(b, pfx+"/*"+(jdoc ? "*" : "")); + for (String line : doco.split("\\R")) { + for (String nl : naturalLines(line)) + w(b, pfx+" * "+nl); + w(b, pfx+" *"); + } + w(b, pfx+" */"); + } + } + + private List naturalLines(String line) { + List lines = new ArrayList<>(); + while (line.length() > 80) { + int cutpoint = 80; + while (cutpoint > 0 && line.charAt(cutpoint) != ' ') { + cutpoint--; + } + if (cutpoint == 0) { + cutpoint = 80; + } else { + cutpoint++; + } + lines.add(line.substring(0, cutpoint)); + line = line.substring(cutpoint); + } + lines.add(line); + return lines; + } + + private void w(StringBuilder b) { + b.append("\r\n"); + + } + + private void w(StringBuilder b, String line) { + b.append(line); + w(b); + } + + private PEGenClass genClass(PEDefinition source) { + PEGenClass cls = new PEGenClass(); + cls.name = source.getProfile().getName(); + cls.base = source.getProfile().getType(); + cls.doco = source.documentation(); + cls.url = source.getProfile().getVersionedUrl(); + cls.isResource = source.getProfile().getKind() == StructureDefinitionKind.RESOURCE; + cls.genId(); + for (PEDefinition child : source.children()) { + if (genForField(source, child)) { + cls.defineField(source, child); + } + } + return cls; + } + + private boolean genForField(PEDefinition source, PEDefinition child) { + if (child.definition().getBase().getPath().equals("Resource.meta")) { + return meta; + } + if (child.definition().getBase().getPath().equals("DomainResource.text")) { + return narrative; + } + if (child.definition().getBase().getPath().equals("Resource.language")) { + return language == null; + } + if (child.definition().getBase().getPath().endsWith(".extension") || child.definition().getBase().getPath().endsWith(".modifierExtension")) { + return extensionPolicy == ExtensionPolicy.Complexes; + } + if (child.definition().getBase().getPath().equals("DomainResource.contained")) { + return contained; + } + return !keyElementsOnly || (child.isKeyElement()); + } + + + private String getPrimitiveType(StructureDefinition sd) { + + if (sd.getType().equals("string")) + return "String"; + if (sd.getType().equals("code")) + return "String"; + if (sd.getType().equals("markdown")) + return "String"; + if (sd.getType().equals("base64Binary")) + return "byte[]"; + if (sd.getType().equals("uri")) + return "String"; + if (sd.getType().equals("url")) + return "String"; + if (sd.getType().equals("canonical")) + return "String"; + if (sd.getType().equals("oid")) + return "String"; + if (sd.getType().equals("integer")) + return "int"; + if (sd.getType().equals("integer64")) + return "long"; + if (sd.getType().equals("unsignedInt")) + return "int"; + if (sd.getType().equals("positiveInt")) + return "int"; + if (sd.getType().equals("boolean")) + return "boolean"; + if (sd.getType().equals("decimal")) + return "BigDecimal"; + if (sd.getType().equals("dateTime")) + return "Date"; + if (sd.getType().equals("date")) + return "Date"; + if (sd.getType().equals("id")) + return "String"; + if (sd.getType().equals("instant")) + return "Date"; + if (sd.getType().equals("time")) + return "String"; + + return "??"; + } + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PEGeneratedBase.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PEGeneratedBase.java new file mode 100644 index 000000000..be9019b04 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/profilemodel/gen/PEGeneratedBase.java @@ -0,0 +1,68 @@ +package org.hl7.fhir.r4.profilemodel.gen; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, \ + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this \ + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, \ + this list of conditions and the following disclaimer in the documentation \ + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \ + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \ + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \ + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \ + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \ + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \ + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \ + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \ + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \ + POSSIBILITY OF SUCH DAMAGE. + */ + +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.profilemodel.PEInstance; + +public class PEGeneratedBase { + + protected IWorkerContext workerContext; + + @Deprecated + protected PEInstance instance; + + @Deprecated + protected void removeChild(String name) { + PEInstance child = instance.child(name); + if (child != null) { + instance.removeChild(child); + } + } + + @Deprecated + protected void removeChildren(String name) { + for (PEInstance child : instance.children(name)) { + instance.removeChild(child); + } + } + + @Deprecated + public PEInstance getInstance() { + return instance; + } + + @Deprecated + public Base getData() { + return instance.getBase(); + } + +} +