From d58188748c26379c67447757fa881fd9822ed9c0 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 1 Nov 2024 08:05:43 +1030 Subject: [PATCH] Clone SQL on FHIR engine to R4, and update FHIRPath engine based on R5 current code --- .../fhir/r4/conformance/ProfileUtilities.java | 2 + .../fhir/r4/context/BaseWorkerContext.java | 67 + .../hl7/fhir/r4/context/ContextUtilities.java | 461 +++ .../hl7/fhir/r4/context/IWorkerContext.java | 3 + .../hl7/fhir/r4/fhirpath/ExpressionNode.java | 1464 +++---- .../org/hl7/fhir/r4/fhirpath/FHIRLexer.java | 304 +- .../hl7/fhir/r4/fhirpath/FHIRPathEngine.java | 3495 +++++++++-------- .../org/hl7/fhir/r4/fhirpath/TypeDetails.java | 375 +- .../main/java/org/hl7/fhir/r4/model/Base.java | 11 + .../java/org/hl7/fhir/r4/model/Constants.java | 2 + .../fhir/r4/model/StructureDefinition.java | 13 + .../r4/profilemodel/gen/PECodeGenerator.java | 2 +- .../org/hl7/fhir/r4/utils/LiquidEngine.java | 4 + .../fhir/r4/utils/StructureMapUtilities.java | 6 +- .../java/org/hl7/fhir/r4/utils/sql/Cell.java | 43 + .../org/hl7/fhir/r4/utils/sql/Column.java | 102 + .../org/hl7/fhir/r4/utils/sql/ColumnKind.java | 5 + .../org/hl7/fhir/r4/utils/sql/Provider.java | 11 + .../org/hl7/fhir/r4/utils/sql/Runner.java | 574 +++ .../org/hl7/fhir/r4/utils/sql/Storage.java | 22 + .../hl7/fhir/r4/utils/sql/StorageJson.java | 105 + .../hl7/fhir/r4/utils/sql/StorageSqlite3.java | 151 + .../java/org/hl7/fhir/r4/utils/sql/Store.java | 19 + .../org/hl7/fhir/r4/utils/sql/Validator.java | 717 ++++ .../java/org/hl7/fhir/r4/utils/sql/Value.java | 130 + .../org/hl7/fhir/r4/test/FHIRPathTests.java | 4 + .../fhir/r4/test/SnapShotGenerationTests.java | 9 + .../org/hl7/fhir/r4b/fhirpath/FHIRLexer.java | 16 +- 28 files changed, 5444 insertions(+), 2673 deletions(-) create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/ContextUtilities.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Cell.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Column.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/ColumnKind.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Provider.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Runner.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Storage.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageJson.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageSqlite3.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Store.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Validator.java create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Value.java 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 42718a344..d74b90a15 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 @@ -282,6 +282,8 @@ public class ProfileUtilities extends TranslatingUtilities { public String url; } + public boolean isPrimitiveType(String typeSimple); + public boolean isDatatype(String typeSimple); public boolean isResource(String typeSimple); 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 4b22b4cc9..3fa18860f 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 @@ -3,6 +3,7 @@ package org.hl7.fhir.r4.context; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -57,6 +58,9 @@ import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorCla import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; import org.hl7.fhir.r4.utils.ToolingExtensions; +import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Library; +import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.utilities.OIDUtils; import org.hl7.fhir.utilities.TranslationServices; import org.hl7.fhir.utilities.Utilities; @@ -894,6 +898,22 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } + @Override + public T fetchResource(Class class_, String uri, Resource source) { + return fetchResource(class_, uri); + } + + @Override + public List fetchTypeDefinitions(String n) { + List types = new ArrayList<>(); + for (StructureDefinition sd : fetchResourcesByType(StructureDefinition.class)) { + if (n.equals(sd.getTypeTail())) { + types.add(sd); + } + } + return types; + } + public T fetchResource(Class class_, String uri, String version) { try { return fetchResourceWithException(class_, uri+"|"+version); @@ -1250,4 +1270,51 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte return corePath + "snomed.html"; return null; } + + @SuppressWarnings("unchecked") + public List fetchResourcesByType(Class class_) { + + List res = new ArrayList<>(); + + synchronized (lock) { + + if (class_ == Resource.class || class_ == DomainResource.class || class_ == null) { + res.addAll((Collection) structures.values()); + res.addAll((Collection) guides.values()); + res.addAll((Collection) capstmts.values()); + res.addAll((Collection) valueSets.values()); + res.addAll((Collection) codeSystems.values()); + res.addAll((Collection) operations.values()); + res.addAll((Collection) searchParameters.values()); + res.addAll((Collection) plans.values()); + res.addAll((Collection) maps.values()); + res.addAll((Collection) transforms.values()); + res.addAll((Collection) questionnaires.values()); + } else if (class_ == ImplementationGuide.class) { + res.addAll((Collection) guides.values()); + } else if (class_ == CapabilityStatement.class) { + res.addAll((Collection) capstmts.values()); + } else if (class_ == StructureDefinition.class) { + res.addAll((Collection) structures.values()); + } else if (class_ == StructureMap.class) { + res.addAll((Collection) transforms.values()); + } else if (class_ == ValueSet.class) { + res.addAll((Collection) valueSets.values()); + } else if (class_ == CodeSystem.class) { + res.addAll((Collection) codeSystems.values()); + } else if (class_ == ConceptMap.class) { + res.addAll((Collection) maps.values()); + } else if (class_ == PlanDefinition.class) { + res.addAll((Collection) plans.values()); + } else if (class_ == OperationDefinition.class) { + res.addAll((Collection) operations.values()); + } else if (class_ == Questionnaire.class) { + res.addAll((Collection) questionnaires.values()); + } else if (class_ == SearchParameter.class) { + res.addAll((Collection) searchParameters.values()); + } + } + return res; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/ContextUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/ContextUtilities.java new file mode 100644 index 000000000..61273e406 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/ContextUtilities.java @@ -0,0 +1,461 @@ +package org.hl7.fhir.r4.context; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.conformance.ProfileUtilities; +import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ElementDefinition; +import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent; +import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; +import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType; +import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r4.model.StructureMap; +import org.hl7.fhir.r4.utils.ToolingExtensions; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.NamingSystem; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.utilities.OIDUtils; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; +import org.hl7.fhir.utilities.validation.ValidationMessage.Source; + +public class ContextUtilities implements ProfileKnowledgeProvider { + + private IWorkerContext context; + private boolean suppressDebugMessages; + private Map oidCache = new HashMap<>(); + private List allStructuresList = new ArrayList(); + private List canonicalResourceNames; + private List concreteResourceNames; + private Set concreteResourceNameSet; + + public ContextUtilities(IWorkerContext context) { + super(); + this.context = context; + } + + public boolean isSuppressDebugMessages() { + return suppressDebugMessages; + } + + public void setSuppressDebugMessages(boolean suppressDebugMessages) { + this.suppressDebugMessages = suppressDebugMessages; + } + + public String oid2Uri(String oid) { + if (oid != null && oid.startsWith("urn:oid:")) { + oid = oid.substring(8); + } + if (oidCache.containsKey(oid)) { + return oidCache.get(oid); + } + + String uri = OIDUtils.getUriForOid(oid); + if (uri != null) { + oidCache.put(oid, uri); + return uri; + } + CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables"); + if (cs != null) { + for (ConceptDefinitionComponent cc : cs.getConcept()) { + for (ConceptPropertyComponent cp : cc.getProperty()) { + if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) { + for (ConceptPropertyComponent cp2 : cc.getProperty()) { + if ("v2-cs-uri".equals(cp2.getCode())) { + oidCache.put(oid, cp2.getValue().primitiveValue()); + return cp2.getValue().primitiveValue(); + } + } + } + } + } + } + for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) { + if (("urn:oid:"+oid).equals(css.getUrl())) { + oidCache.put(oid, css.getUrl()); + return css.getUrl(); + } + for (Identifier id : css.getIdentifier()) { + if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) { + oidCache.put(oid, css.getUrl()); + return css.getUrl(); + } + } + } + for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) { + if (hasOid(ns, oid)) { + uri = getUri(ns); + if (uri != null) { + oidCache.put(oid, null); + return null; + } + } + } + oidCache.put(oid, null); + return null; + } + + private String getUri(NamingSystem ns) { + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + if (id.getType() == NamingSystemIdentifierType.URI) + return id.getValue(); + } + return null; + } + + private boolean hasOid(NamingSystem ns, String oid) { + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) + return true; + } + return false; + } + + /** + * @return a list of the resource and type names defined for this version + */ + public List getTypeNames() { + Set result = new HashSet(); + for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { + if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) + result.add(sd.getName()); + } + return Utilities.sorted(result); + } + + + /** + * @return a set of the resource and type names defined for this version + */ + public Set getTypeNameSet() { + Set result = new HashSet(); + for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { + if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && + VersionUtilities.versionsCompatible(context.getVersion(), sd.getFhirVersion().toCode())) { + result.add(sd.getName()); + } + } + return result; + } + + public String getLinkForUrl(String corePath, String url) { + if (url == null) { + return null; + } + + if (context.hasResource(Resource.class, url)) { + Resource cr = context.fetchResource(Resource.class, url); + return cr.getUserString("path"); + } + return null; + } + + + protected String tail(String url) { + if (Utilities.noString(url)) { + return "noname"; + } + if (url.contains("/")) { + return url.substring(url.lastIndexOf("/")+1); + } + return url; + } + + private boolean hasUrlProperty(StructureDefinition sd) { + for (ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(sd.getType()+".url")) { + return true; + } + } + return false; + } + + // -- profile services --------------------------------------------------------- + + + /** + * @return a list of the resource names that are canonical resources defined for this version + */ + public List getCanonicalResourceNames() { + if (canonicalResourceNames == null) { + canonicalResourceNames = new ArrayList<>(); + Set names = new HashSet<>(); + for (StructureDefinition sd : allStructures()) { + if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) { + names.add(sd.getType()); + } + } + canonicalResourceNames.addAll(Utilities.sorted(names)); + } + return canonicalResourceNames; + } + + /** + * @return a list of all structure definitions, with snapshots generated (if possible) + */ + public List allStructures(){ + if (allStructuresList.isEmpty()) { + Set set = new HashSet(); + for (StructureDefinition sd : getStructures()) { + if (!set.contains(sd)) { + try { + generateSnapshot(sd); + // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); + } catch (Exception e) { + if (!isSuppressDebugMessages()) { + System.out.println("Unable to generate snapshot @2 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); + if (context.getLogger() != null) { + e.printStackTrace(); + } + } + } + allStructuresList.add(sd); + set.add(sd); + } + } + } + return allStructuresList; + } + + /** + * @return a list of all structure definitions, without trying to generate snapshots + */ + public List getStructures() { + return context.fetchResourcesByType(StructureDefinition.class); + } + + /** + * Given a structure definition, generate a snapshot (or regenerate it) + * @param p + * @throws DefinitionException + * @throws FHIRException + */ + public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { + if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p))) { + if (!p.hasBaseDefinition()) + throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl())); + StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p); + if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) { + throw new Error("Not done yet"); // sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion()); + } + if (sd == null) { + throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition())); + } + List msgs = new ArrayList(); + List errors = new ArrayList(); + ProfileUtilities pu = new ProfileUtilities(context, msgs, this); + pu.setThrowException(false); + if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { + pu.sortDifferential(sd, p, p.getUrl(), errors); + } + pu.setDebug(false); + for (String err : errors) { + msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); + } + pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); + for (ValidationMessage msg : msgs) { + if ((msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) { + if (!msg.isIgnorableError()) { + throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); + } else { + System.err.println(msg.getMessage()); + } + } + } + if (!p.hasSnapshot()) + throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl())); + pu = null; + } + } + + + // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind. + private boolean isProfileNeedsRegenerate(StructureDefinition p) { + boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse"); + if (needs) { + p.setUserData("hack.regnerated", "yes"); + } + return needs; + } + + @Override + public boolean isPrimitiveType(String type) { + return context.isPrimitiveType(type); + } + + @Override + public boolean isDatatype(String type) { + StructureDefinition sd = context.fetchTypeDefinition(type); + return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; + } + + @Override + public boolean isResource(String t) { + if (getConcreteResourceSet().contains(t)) { + return true; + } + StructureDefinition sd; + try { + sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); + } catch (Exception e) { + return false; + } + if (sd == null) + return false; + if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) + return false; + return sd.getKind() == StructureDefinitionKind.RESOURCE; + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return false; + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + return null; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) { + return null; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) { + return null; + } + + @Override + public String getLinkForProfile(StructureDefinition profile, String url) { + return null; + } + @Override + public boolean prependLinks() { + return false; + } + + public StructureDefinition fetchByJsonName(String key) { + for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { + ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); + if (ed != null) { + return sd; + } + } + return null; + } + + public Set getConcreteResourceSet() { + if (concreteResourceNameSet == null) { + concreteResourceNameSet = new HashSet<>(); + for (StructureDefinition sd : getStructures()) { + if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + concreteResourceNameSet.add(sd.getType()); + } + } + } + return concreteResourceNameSet; + } + + public List getConcreteResources() { + if (concreteResourceNames == null) { + concreteResourceNames = new ArrayList<>(); + concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet())); + } + return concreteResourceNames; + } + + public List listMaps(String url) { + List res = new ArrayList<>(); + String start = url.substring(0, url.indexOf("*")); + String end = url.substring(url.indexOf("*")+1); + for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) { + String u = map.getUrl(); + if (u.startsWith(start) && u.endsWith(end)) { + res.add(map); + } + } + return res; + } + + public List fetchCodeSystemVersions(String system) { + List res = new ArrayList<>(); + for (CodeSystem cs : context.fetchResourcesByType(CodeSystem.class)) { + if (system.equals(cs.getUrl()) && cs.hasVersion()) { + res.add(cs.getVersion()); + } + } + return res; + } + + public StructureDefinition findType(String typeName) { + StructureDefinition t = context.fetchTypeDefinition(typeName); + if (t != null) { + return t; + } + List candidates = new ArrayList<>(); + for (StructureDefinition sd : getStructures()) { + if (sd.getType().equals(typeName)) { + candidates.add(sd); + } + } + if (candidates.size() == 1) { + return candidates.get(0); + } + return null; + } + + public StructureDefinition fetchProfileByIdentifier(String tid) { + for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { + for (Identifier ii : sd.getIdentifier()) { + if (tid.equals(ii.getValue())) { + return sd; + } + } + } + return null; + } + + public boolean isAbstractType(String typeName) { + StructureDefinition sd = context.fetchTypeDefinition(typeName); + if (sd != null) { + return sd.getAbstract(); + } + return false; + } + + public boolean isDomainResource(String typeName) { + StructureDefinition sd = context.fetchTypeDefinition(typeName); + while (sd != null) { + if ("DomainResource".equals(sd.getType())) { + return true; + } + sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + } + + public IWorkerContext getWorker() { + return context; + } + +} + 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 63bba4d35..d6bae3140 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 @@ -169,8 +169,10 @@ public interface IWorkerContext { */ public T fetchResource(Class class_, String uri); public T fetchResource(Class class_, String uri, String version); + public T fetchResource(Class class_, String uri, Resource source); public T fetchResourceWithException(Class class_, String uri) throws FHIRException; + public List fetchResourcesByType(Class class_); /** * Variation of fetchResource when you have a string type, and don't need the @@ -485,6 +487,7 @@ public interface IWorkerContext { public void setOverrideVersionNs(String value); public StructureDefinition fetchTypeDefinition(String typeName); + public List fetchTypeDefinitions(String n); public void setUcumService(UcumService ucumService); diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java index a2bf898b7..34a399ccd 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/ExpressionNode.java @@ -41,457 +41,262 @@ import org.hl7.fhir.utilities.Utilities; public class ExpressionNode { public enum Kind { - Name, Function, Constant, Group, Unary - } + Name, Function, Constant, Group, Unary + } public enum Function { - Custom, - - Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, - Item /* implicit from name[] */, As, Is, Single, First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, - Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches, - Contains, Replace, Length, Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse, - AnyFalse, AllTrue, AnyTrue, HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, - ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, - ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, - Power, Truncate, - + Custom, + + Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single, + First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, IndexOf, Substring, StartsWith, EndsWith, Matches, MatchesFull, ReplaceMatches, Contains, Replace, Length, + Children, Descendants, MemberOf, Trace, DefineVariable, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue, + HasValue, OfType, Type, ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToDate, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo, + Round, Sqrt, Abs, Ceiling, Exp, Floor, Ln, Log, Power, Truncate, + // R3 functions Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, - + // Local extensions to FHIRPath - HtmlChecks1, HtmlChecks2, Comparable; + HtmlChecks1, HtmlChecks2, Comparable, hasTemplateIdOf; public static Function fromCode(String name) { - if (name.equals("empty")) - return Function.Empty; - if (name.equals("not")) - return Function.Not; - if (name.equals("exists")) - return Function.Exists; - if (name.equals("subsetOf")) - return Function.SubsetOf; - if (name.equals("supersetOf")) - return Function.SupersetOf; - if (name.equals("isDistinct")) - return Function.IsDistinct; - if (name.equals("distinct")) - return Function.Distinct; - if (name.equals("count")) - return Function.Count; - if (name.equals("where")) - return Function.Where; - if (name.equals("select")) - return Function.Select; - if (name.equals("all")) - return Function.All; - if (name.equals("repeat")) - return Function.Repeat; - if (name.equals("aggregate")) - return Function.Aggregate; - if (name.equals("item")) - return Function.Item; - if (name.equals("as")) - return Function.As; - if (name.equals("is")) - return Function.Is; - if (name.equals("single")) - return Function.Single; - if (name.equals("first")) - return Function.First; - if (name.equals("last")) - return Function.Last; - if (name.equals("tail")) - return Function.Tail; - if (name.equals("skip")) - return Function.Skip; - if (name.equals("take")) - return Function.Take; - if (name.equals("union")) - return Function.Union; - if (name.equals("combine")) - return Function.Combine; - if (name.equals("intersect")) - return Function.Intersect; - if (name.equals("exclude")) - return Function.Exclude; - if (name.equals("iif")) - return Function.Iif; - if (name.equals("lower")) - return Function.Lower; - if (name.equals("upper")) - return Function.Upper; - if (name.equals("toChars")) - return Function.ToChars; - if (name.equals("indexOf")) - return Function.IndexOf; - if (name.equals("substring")) - return Function.Substring; - if (name.equals("startsWith")) - return Function.StartsWith; - if (name.equals("endsWith")) - return Function.EndsWith; - if (name.equals("matches")) - return Function.Matches; - if (name.equals("matchesFull")) - return Function.MatchesFull; - if (name.equals("replaceMatches")) - return Function.ReplaceMatches; - if (name.equals("contains")) - return Function.Contains; - if (name.equals("replace")) - return Function.Replace; - if (name.equals("length")) - return Function.Length; - if (name.equals("children")) - return Function.Children; - if (name.equals("descendants")) - return Function.Descendants; - if (name.equals("memberOf")) - return Function.MemberOf; - if (name.equals("trace")) - return Function.Trace; - if (name.equals("defineVariable")) - return Function.DefineVariable; - if (name.equals("check")) - return Function.Check; - if (name.equals("today")) - return Function.Today; - if (name.equals("now")) - return Function.Now; - if (name.equals("resolve")) - return Function.Resolve; - if (name.equals("extension")) - return Function.Extension; - if (name.equals("allFalse")) - return Function.AllFalse; - if (name.equals("anyFalse")) - return Function.AnyFalse; - if (name.equals("allTrue")) - return Function.AllTrue; - if (name.equals("anyTrue")) - return Function.AnyTrue; - if (name.equals("hasValue")) - return Function.HasValue; - if (name.equals("htmlChecks")) - return Function.HtmlChecks1; - if (name.equals("htmlchecks")) - return Function.HtmlChecks1; // support change of care from R3 - if (name.equals("htmlChecks2")) - return Function.HtmlChecks2; - if (name.equals("comparable")) - return Function.Comparable; - if (name.equals("encode")) - return Function.Encode; - if (name.equals("decode")) - return Function.Decode; - if (name.equals("escape")) - return Function.Escape; - if (name.equals("unescape")) - return Function.Unescape; - if (name.equals("trim")) - return Function.Trim; - if (name.equals("split")) - return Function.Split; - if (name.equals("join")) - return Function.Join; - if (name.equals("ofType")) - return Function.OfType; - if (name.equals("type")) - return Function.Type; - if (name.equals("toInteger")) - return Function.ToInteger; - if (name.equals("toDecimal")) - return Function.ToDecimal; - if (name.equals("toString")) - return Function.ToString; - if (name.equals("toQuantity")) - return Function.ToQuantity; - if (name.equals("toBoolean")) - return Function.ToBoolean; - if (name.equals("toDateTime")) - return Function.ToDateTime; - if (name.equals("toTime")) - return Function.ToTime; - if (name.equals("convertsToInteger")) - return Function.ConvertsToInteger; - if (name.equals("convertsToDecimal")) - return Function.ConvertsToDecimal; - if (name.equals("convertsToString")) - return Function.ConvertsToString; - if (name.equals("convertsToQuantity")) - return Function.ConvertsToQuantity; - if (name.equals("convertsToBoolean")) - return Function.ConvertsToBoolean; - if (name.equals("convertsToDateTime")) - return Function.ConvertsToDateTime; - if (name.equals("convertsToDate")) - return Function.ConvertsToDate; - if (name.equals("convertsToTime")) - return Function.ConvertsToTime; - if (name.equals("conformsTo")) - return Function.ConformsTo; - if (name.equals("round")) - return Function.Round; - if (name.equals("sqrt")) - return Function.Sqrt; - if (name.equals("abs")) - return Function.Abs; - if (name.equals("ceiling")) - return Function.Ceiling; - if (name.equals("exp")) - return Function.Exp; - if (name.equals("floor")) - return Function.Floor; - if (name.equals("ln")) - return Function.Ln; - if (name.equals("log")) - return Function.Log; - if (name.equals("power")) - return Function.Power; - if (name.equals("truncate")) - return Function.Truncate; - if (name.equals("lowBoundary")) - return Function.LowBoundary; - if (name.equals("highBoundary")) - return Function.HighBoundary; - if (name.equals("precision")) - return Function.Precision; + if (name.equals("empty")) return Function.Empty; + if (name.equals("not")) return Function.Not; + if (name.equals("exists")) return Function.Exists; + if (name.equals("subsetOf")) return Function.SubsetOf; + if (name.equals("supersetOf")) return Function.SupersetOf; + if (name.equals("isDistinct")) return Function.IsDistinct; + if (name.equals("distinct")) return Function.Distinct; + if (name.equals("count")) return Function.Count; + if (name.equals("where")) return Function.Where; + if (name.equals("select")) return Function.Select; + if (name.equals("all")) return Function.All; + if (name.equals("repeat")) return Function.Repeat; + if (name.equals("aggregate")) return Function.Aggregate; + if (name.equals("item")) return Function.Item; + if (name.equals("as")) return Function.As; + if (name.equals("is")) return Function.Is; + if (name.equals("single")) return Function.Single; + if (name.equals("first")) return Function.First; + if (name.equals("last")) return Function.Last; + if (name.equals("tail")) return Function.Tail; + if (name.equals("skip")) return Function.Skip; + if (name.equals("take")) return Function.Take; + if (name.equals("union")) return Function.Union; + if (name.equals("combine")) return Function.Combine; + if (name.equals("intersect")) return Function.Intersect; + if (name.equals("exclude")) return Function.Exclude; + if (name.equals("iif")) return Function.Iif; + if (name.equals("lower")) return Function.Lower; + if (name.equals("upper")) return Function.Upper; + if (name.equals("toChars")) return Function.ToChars; + if (name.equals("indexOf")) return Function.IndexOf; + if (name.equals("substring")) return Function.Substring; + if (name.equals("startsWith")) return Function.StartsWith; + if (name.equals("endsWith")) return Function.EndsWith; + if (name.equals("matches")) return Function.Matches; + if (name.equals("matchesFull")) return Function.MatchesFull; + if (name.equals("replaceMatches")) return Function.ReplaceMatches; + if (name.equals("contains")) return Function.Contains; + if (name.equals("replace")) return Function.Replace; + if (name.equals("length")) return Function.Length; + if (name.equals("children")) return Function.Children; + if (name.equals("descendants")) return Function.Descendants; + if (name.equals("memberOf")) return Function.MemberOf; + if (name.equals("trace")) return Function.Trace; + if (name.equals("defineVariable")) return Function.DefineVariable; + if (name.equals("check")) return Function.Check; + if (name.equals("today")) return Function.Today; + if (name.equals("now")) return Function.Now; + if (name.equals("resolve")) return Function.Resolve; + if (name.equals("extension")) return Function.Extension; + if (name.equals("allFalse")) return Function.AllFalse; + if (name.equals("anyFalse")) return Function.AnyFalse; + if (name.equals("allTrue")) return Function.AllTrue; + if (name.equals("anyTrue")) return Function.AnyTrue; + if (name.equals("hasValue")) return Function.HasValue; + if (name.equals("htmlChecks")) return Function.HtmlChecks1; + if (name.equals("htmlchecks")) return Function.HtmlChecks1; // support change of care from R3 + if (name.equals("htmlChecks2")) return Function.HtmlChecks2; + if (name.equals("comparable")) return Function.Comparable; + if (name.equals("encode")) return Function.Encode; + if (name.equals("decode")) return Function.Decode; + if (name.equals("escape")) return Function.Escape; + if (name.equals("unescape")) return Function.Unescape; + if (name.equals("trim")) return Function.Trim; + if (name.equals("split")) return Function.Split; + if (name.equals("join")) return Function.Join; + if (name.equals("ofType")) return Function.OfType; + if (name.equals("type")) return Function.Type; + if (name.equals("toInteger")) return Function.ToInteger; + if (name.equals("toDecimal")) return Function.ToDecimal; + if (name.equals("toString")) return Function.ToString; + if (name.equals("toQuantity")) return Function.ToQuantity; + if (name.equals("toBoolean")) return Function.ToBoolean; + if (name.equals("toDateTime")) return Function.ToDateTime; + if (name.equals("toTime")) return Function.ToTime; + if (name.equals("convertsToInteger")) return Function.ConvertsToInteger; + if (name.equals("convertsToDecimal")) return Function.ConvertsToDecimal; + if (name.equals("convertsToString")) return Function.ConvertsToString; + if (name.equals("convertsToQuantity")) return Function.ConvertsToQuantity; + if (name.equals("convertsToBoolean")) return Function.ConvertsToBoolean; + if (name.equals("convertsToDateTime")) return Function.ConvertsToDateTime; + if (name.equals("convertsToDate")) return Function.ConvertsToDate; + if (name.equals("convertsToTime")) return Function.ConvertsToTime; + if (name.equals("conformsTo")) return Function.ConformsTo; + if (name.equals("round")) return Function.Round; + if (name.equals("sqrt")) return Function.Sqrt; + if (name.equals("abs")) return Function.Abs; + if (name.equals("ceiling")) return Function.Ceiling; + if (name.equals("exp")) return Function.Exp; + if (name.equals("floor")) return Function.Floor; + if (name.equals("ln")) return Function.Ln; + if (name.equals("log")) return Function.Log; + if (name.equals("power")) return Function.Power; + if (name.equals("truncate")) return Function.Truncate; + if (name.equals("lowBoundary")) return Function.LowBoundary; + if (name.equals("highBoundary")) return Function.HighBoundary; + if (name.equals("precision")) return Function.Precision; + if (name.equals("hasTemplateIdOf")) return Function.hasTemplateIdOf; return null; } - + public String toCode() { switch (this) { - case Empty: - return "empty"; - case Not: - return "not"; - case Exists: - return "exists"; - case SubsetOf: - return "subsetOf"; - case SupersetOf: - return "supersetOf"; - case IsDistinct: - return "isDistinct"; - case Distinct: - return "distinct"; - case Count: - return "count"; - case Where: - return "where"; - case Select: - return "select"; - case All: - return "all"; - case Repeat: - return "repeat"; - case Aggregate: - return "aggregate"; - case Item: - return "item"; - case As: - return "as"; - case Is: - return "is"; - case Single: - return "single"; - case First: - return "first"; - case Last: - return "last"; - case Tail: - return "tail"; - case Skip: - return "skip"; - case Take: - return "take"; - case Union: - return "union"; - case Combine: - return "combine"; - case Intersect: - return "intersect"; - case Exclude: - return "exclude"; - case Iif: - return "iif"; - case ToChars: - return "toChars"; - case Lower: - return "lower"; - case Upper: - return "upper"; - case IndexOf: - return "indexOf"; - case Substring: - return "substring"; - case StartsWith: - return "startsWith"; - case EndsWith: - return "endsWith"; - case Matches: - return "matches"; - case MatchesFull: - return "matchesFull"; - case ReplaceMatches: - return "replaceMatches"; - case Contains: - return "contains"; - case Replace: - return "replace"; - case Length: - return "length"; - case Children: - return "children"; - case Descendants: - return "descendants"; - case MemberOf: - return "memberOf"; - case Trace: - return "trace"; - case DefineVariable : - return "defineVariable"; - case Check: - return "check"; - case Today: - return "today"; - case Now: - return "now"; - case Resolve: - return "resolve"; - case Extension: - return "extension"; - case AllFalse: - return "allFalse"; - case AnyFalse: - return "anyFalse"; - case AllTrue: - return "allTrue"; - case AnyTrue: - return "anyTrue"; - case HasValue: - return "hasValue"; - case Encode: - return "encode"; - case Decode: - return "decode"; - case Escape: - return "escape"; - case Unescape: - return "unescape"; - case Trim: - return "trim"; - case Split: - return "split"; - case Join: - return "join"; - case HtmlChecks1: - return "htmlChecks"; - case HtmlChecks2: - return "htmlChecks2"; - case Comparable: - return "comparable"; - case OfType: - return "ofType"; - case Type: - return "type"; - case ToInteger: - return "toInteger"; - case ToDecimal: - return "toDecimal"; - case ToString: - return "toString"; - case ToBoolean: - return "toBoolean"; - case ToQuantity: - return "toQuantity"; - case ToDateTime: - return "toDateTime"; - case ToTime: - return "toTime"; - case ConvertsToInteger: - return "convertsToInteger"; - case ConvertsToDecimal: - return "convertsToDecimal"; - case ConvertsToString: - return "convertsToString"; - case ConvertsToBoolean: - return "convertsToBoolean"; - case ConvertsToQuantity: - return "convertsToQuantity"; - case ConvertsToDateTime: - return "convertsToDateTime"; - case ConvertsToDate: - return "convertsToDate"; - case ConvertsToTime: - return "isTime"; - case ConformsTo: - return "conformsTo"; - case Round: - return "round"; - case Sqrt: - return "sqrt"; - case Abs: - return "abs"; - case Ceiling: - return "ceiling"; - case Exp: - return "exp"; - case Floor: - return "floor"; - case Ln: - return "ln"; - case Log: - return "log"; - case Power: - return "power"; - case Truncate: - return "truncate"; - case LowBoundary: - return "lowBoundary"; - case HighBoundary: - return "highBoundary"; - case Precision: - return "precision"; - default: - return "?custom?"; + case Empty : return "empty"; + case Not : return "not"; + case Exists : return "exists"; + case SubsetOf : return "subsetOf"; + case SupersetOf : return "supersetOf"; + case IsDistinct : return "isDistinct"; + case Distinct : return "distinct"; + case Count : return "count"; + case Where : return "where"; + case Select : return "select"; + case All : return "all"; + case Repeat : return "repeat"; + case Aggregate : return "aggregate"; + case Item : return "item"; + case As : return "as"; + case Is : return "is"; + case Single : return "single"; + case First : return "first"; + case Last : return "last"; + case Tail : return "tail"; + case Skip : return "skip"; + case Take : return "take"; + case Union : return "union"; + case Combine : return "combine"; + case Intersect : return "intersect"; + case Exclude : return "exclude"; + case Iif : return "iif"; + case ToChars : return "toChars"; + case Lower : return "lower"; + case Upper : return "upper"; + case IndexOf : return "indexOf"; + case Substring : return "substring"; + case StartsWith : return "startsWith"; + case EndsWith : return "endsWith"; + case Matches : return "matches"; + case MatchesFull : return "matchesFull"; + case ReplaceMatches : return "replaceMatches"; + case Contains : return "contains"; + case Replace : return "replace"; + case Length : return "length"; + case Children : return "children"; + case Descendants : return "descendants"; + case MemberOf : return "memberOf"; + case Trace : return "trace"; + case DefineVariable : return "defineVariable"; + case Check : return "check"; + case Today : return "today"; + case Now : return "now"; + case Resolve : return "resolve"; + case Extension : return "extension"; + case AllFalse : return "allFalse"; + case AnyFalse : return "anyFalse"; + case AllTrue : return "allTrue"; + case AnyTrue : return "anyTrue"; + case HasValue : return "hasValue"; + case Encode : return "encode"; + case Decode : return "decode"; + case Escape : return "escape"; + case Unescape : return "unescape"; + case Trim : return "trim"; + case Split : return "split"; + case Join : return "join"; + case HtmlChecks1 : return "htmlChecks"; + case HtmlChecks2 : return "htmlChecks2"; + case Comparable : return "comparable"; + case OfType : return "ofType"; + case Type : return "type"; + case ToInteger : return "toInteger"; + case ToDecimal : return "toDecimal"; + case ToString : return "toString"; + case ToBoolean : return "toBoolean"; + case ToQuantity : return "toQuantity"; + case ToDateTime : return "toDateTime"; + case ToTime : return "toTime"; + case ConvertsToInteger : return "convertsToInteger"; + case ConvertsToDecimal : return "convertsToDecimal"; + case ConvertsToString : return "convertsToString"; + case ConvertsToBoolean : return "convertsToBoolean"; + case ConvertsToQuantity : return "convertsToQuantity"; + case ConvertsToDateTime : return "convertsToDateTime"; + case ConvertsToDate : return "convertsToDate"; + case ConvertsToTime : return "isTime"; + case ConformsTo : return "conformsTo"; + case Round : return "round"; + case Sqrt : return "sqrt"; + case Abs : return "abs"; + case Ceiling : return "ceiling"; + case Exp : return "exp"; + case Floor : return "floor"; + case Ln : return "ln"; + case Log : return "log"; + case Power : return "power"; + case Truncate: return "truncate"; + case LowBoundary: return "lowBoundary"; + case HighBoundary: return "highBoundary"; + case Precision: return "precision"; + case hasTemplateIdOf: return "hasTemplateIdOf"; + default: return "?custom?"; } } } - public enum Operation { - Equals, Equivalent, NotEquals, NotEquivalent, LessThan, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, - And, Xor, Implies, Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains, MemberOf; + public enum Operation { + Equals, Equivalent, NotEquals, NotEquivalent, LessThan, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, And, Xor, Implies, + Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains, MemberOf; - public static Operation fromCode(String name) { - if (Utilities.noString(name)) - return null; - if (name.equals("=")) - return Operation.Equals; - if (name.equals("~")) - return Operation.Equivalent; - if (name.equals("!=")) - return Operation.NotEquals; - if (name.equals("!~")) - return Operation.NotEquivalent; - if (name.equals(">")) - return Operation.Greater; - if (name.equals("<")) - return Operation.LessThan; - if (name.equals(">=")) - return Operation.GreaterOrEqual; - if (name.equals("<=")) - return Operation.LessOrEqual; - if (name.equals("|")) - return Operation.Union; - if (name.equals("or")) - return Operation.Or; - if (name.equals("and")) - return Operation.And; - if (name.equals("xor")) - return Operation.Xor; + public static Operation fromCode(String name) { + if (Utilities.noString(name)) + return null; + if (name.equals("=")) + return Operation.Equals; + if (name.equals("~")) + return Operation.Equivalent; + if (name.equals("!=")) + return Operation.NotEquals; + if (name.equals("!~")) + return Operation.NotEquivalent; + if (name.equals(">")) + return Operation.Greater; + if (name.equals("<")) + return Operation.LessThan; + if (name.equals(">=")) + return Operation.GreaterOrEqual; + if (name.equals("<=")) + return Operation.LessOrEqual; + if (name.equals("|")) + return Operation.Union; + if (name.equals("or")) + return Operation.Or; + if (name.equals("and")) + return Operation.And; + if (name.equals("xor")) + return Operation.Xor; if (name.equals("is")) return Operation.Is; if (name.equals("as")) @@ -500,14 +305,14 @@ public class ExpressionNode { return Operation.Times; if (name.equals("/")) return Operation.DivideBy; - if (name.equals("+")) - return Operation.Plus; + if (name.equals("+")) + return Operation.Plus; if (name.equals("-")) return Operation.Minus; if (name.equals("&")) return Operation.Concatenate; - if (name.equals("implies")) - return Operation.Implies; + if (name.equals("implies")) + return Operation.Implies; if (name.equals("div")) return Operation.Div; if (name.equals("mod")) @@ -517,126 +322,103 @@ public class ExpressionNode { if (name.equals("contains")) return Operation.Contains; if (name.equals("memberOf")) - return Operation.MemberOf; - return null; + return Operation.MemberOf; + return null; - } - - public String toCode() { - switch (this) { - case Equals: - return "="; - case Equivalent: - return "~"; - case NotEquals: - return "!="; - case NotEquivalent: - return "!~"; - case Greater: - return ">"; - case LessThan: - return "<"; - case GreaterOrEqual: - return ">="; - case LessOrEqual: - return "<="; - case Union: - return "|"; - case Or: - return "or"; - case And: - return "and"; - case Xor: - return "xor"; - case Times: - return "*"; - case DivideBy: - return "/"; - case Plus: - return "+"; - case Minus: - return "-"; - case Concatenate: - return "&"; - case Implies: - return "implies"; - case Is: - return "is"; - case As: - return "as"; - case Div: - return "div"; - case Mod: - return "mod"; - case In: - return "in"; - case Contains: - return "contains"; - case MemberOf: - return "memberOf"; - default: - return "?custom?"; - } - } - } + } + public String toCode() { + switch (this) { + case Equals : return "="; + case Equivalent : return "~"; + case NotEquals : return "!="; + case NotEquivalent : return "!~"; + case Greater : return ">"; + case LessThan : return "<"; + case GreaterOrEqual : return ">="; + case LessOrEqual : return "<="; + case Union : return "|"; + case Or : return "or"; + case And : return "and"; + case Xor : return "xor"; + case Times : return "*"; + case DivideBy : return "/"; + case Plus : return "+"; + case Minus : return "-"; + case Concatenate : return "&"; + case Implies : return "implies"; + case Is : return "is"; + case As : return "as"; + case Div : return "div"; + case Mod : return "mod"; + case In : return "in"; + case Contains : return "contains"; + case MemberOf : return "memberOf"; + default: return "?custom?"; + } + } + } public enum CollectionStatus { SINGLETON, ORDERED, UNORDERED; + + boolean isList() { + return this == ORDERED || this == UNORDERED; + } } + + //the expression will have one of either name or constant + private String uniqueId; + private Kind kind; + private String name; + private Base constant; + private Function function; + private List parameters; // will be created if there is a function + private ExpressionNode inner; + private ExpressionNode group; + private Operation operation; + private boolean proximal; // a proximal operation is the first in the sequence of operations. This is significant when evaluating the outcomes + private ExpressionNode opNext; + private SourceLocation start; + private SourceLocation end; + private SourceLocation opStart; + private SourceLocation opEnd; + private TypeDetails types; + private TypeDetails opTypes; - // the expression will have one of either name or constant - private String uniqueId; - private Kind kind; - private String name; - private Base constant; - private Function function; - private List parameters; // will be created if there is a function - private ExpressionNode inner; - private ExpressionNode group; - private Operation operation; - private boolean proximal; // a proximal operation is the first in the sequence of operations. This is - // significant when evaluating the outcomes - private ExpressionNode opNext; - private SourceLocation start; - private SourceLocation end; - private SourceLocation opStart; - private SourceLocation opEnd; - private TypeDetails types; - private TypeDetails opTypes; - public ExpressionNode(int uniqueId) { - super(); - this.uniqueId = Integer.toString(uniqueId); - } + public ExpressionNode(int uniqueId) { + super(); + this.uniqueId = Integer.toString(uniqueId); + } - public String toString() { - StringBuilder b = new StringBuilder(); - switch (kind) { - case Name: - b.append(name); - break; - case Function: - if (function == Function.Item) - b.append("["); - else { - b.append(name); - b.append("("); - } - boolean first = true; - for (ExpressionNode n : parameters) { - if (first) - first = false; - else - b.append(", "); - b.append(n.toString()); - } - if (function == Function.Item) { + public String toString() { + StringBuilder b = new StringBuilder(); + switch (kind) { + case Name: + b.append(name); + break; + case Function: + if (function == Function.Item) + b.append("["); + else { + b.append(name); + b.append("("); + } + boolean first = true; + for (ExpressionNode n : parameters) { + if (first) + first = false; + else + b.append(", "); + b.append(n.toString()); + } + if (function == Function.Item) { b.append("]"); } else { - b.append(")"); - } - break; - case Constant: + b.append(")"); + } + break; + case Constant: if (constant == null) { b.append("{}"); } else if (constant instanceof StringType) { @@ -644,301 +426,313 @@ public class ExpressionNode { } else if (constant instanceof Quantity) { Quantity q = (Quantity) constant; b.append(Utilities.escapeJson(q.getValue().toPlainString())); - b.append(" '"); - b.append(Utilities.escapeJson(q.getUnit())); - b.append("'"); + if (q.hasUnit() || q.hasCode()) { + b.append(" '"); + if (q.hasUnit()) { + b.append(Utilities.escapeJson(q.getUnit())); + } else { + b.append(Utilities.escapeJson(q.getCode())); + } + b.append("'"); + } } else if (constant.primitiveValue() != null) { b.append(Utilities.escapeJson(constant.primitiveValue())); } else { b.append(Utilities.escapeJson(constant.toString())); } - break; - case Group: - b.append("("); - b.append(group.toString()); - b.append(")"); - } - if (inner != null) { - if (!((ExpressionNode.Kind.Function == inner.getKind()) - && (ExpressionNode.Function.Item == inner.getFunction()))) { - b.append("."); - } - b.append(inner.toString()); - } + break; + case Group: + b.append("("); + b.append(group.toString()); + b.append(")"); + } + if (inner != null) { + if (!((ExpressionNode.Kind.Function == inner.getKind()) && (ExpressionNode.Function.Item == inner.getFunction()))) { + b.append("."); + } + b.append(inner.toString()); + } + if (operation != null) { + b.append(" "); + b.append(operation.toCode()); + b.append(" "); + b.append(opNext.toString()); + } + + return b.toString(); + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Base getConstant() { + return constant; + } + public void setConstant(Base constant) { + this.constant = constant; + } + + public Function getFunction() { + return function; + } + public void setFunction(Function function) { + this.function = function; + if (parameters == null) + parameters = new ArrayList(); + } + + public boolean isProximal() { + return proximal; + } + public void setProximal(boolean proximal) { + this.proximal = proximal; + } + public Operation getOperation() { + return operation; + } + public void setOperation(Operation operation) { + this.operation = operation; + } + public ExpressionNode getInner() { + return inner; + } + public void setInner(ExpressionNode value) { + this.inner = value; + } + public ExpressionNode getOpNext() { + return opNext; + } + public void setOpNext(ExpressionNode value) { + this.opNext = value; + } + public List getParameters() { + return parameters; + } + public boolean checkName() { + if (!name.startsWith("$")) + return true; + else + return Utilities.existsInList(name, "$this", "$total", "$index"); + } + + public Kind getKind() { + return kind; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public ExpressionNode getGroup() { + return group; + } + + public void setGroup(ExpressionNode group) { + this.group = group; + } + + public SourceLocation getStart() { + return start; + } + + public void setStart(SourceLocation start) { + this.start = start; + } + + public SourceLocation getEnd() { + return end; + } + + public void setEnd(SourceLocation end) { + this.end = end; + } + + public SourceLocation getOpStart() { + return opStart; + } + + public void setOpStart(SourceLocation opStart) { + this.opStart = opStart; + } + + public SourceLocation getOpEnd() { + return opEnd; + } + + public void setOpEnd(SourceLocation opEnd) { + this.opEnd = opEnd; + } + + public String getUniqueId() { + return uniqueId; + } + + + public int parameterCount() { + if (parameters == null) + return 0; + else + return parameters.size(); + } + + public String Canonical() { + StringBuilder b = new StringBuilder(); + write(b); + return b.toString(); + } + + public String summary() { + switch (kind) { + case Name: return uniqueId+": "+name; + case Function: return uniqueId+": "+function.toString()+"()"; + case Constant: return uniqueId+": "+constant; + case Group: return uniqueId+": (Group)"; + } + return "?exp-kind?"; + } + + private void write(StringBuilder b) { + + switch (kind) { + case Name: + b.append(name); + break; + case Constant: + b.append(constant); + break; + case Function: + b.append(function.toCode()); + b.append('('); + boolean f = true; + for (ExpressionNode n : parameters) { + if (f) + f = false; + else + b.append(", "); + n.write(b); + } + b.append(')'); + + break; + case Group: + b.append('('); + group.write(b); + b.append(')'); + } + + if (inner != null) { + b.append('.'); + inner.write(b); + } + if (operation != null) { + b.append(' '); + b.append(operation.toCode()); + b.append(' '); + opNext.write(b); + } + } + + public String check() { + + if (kind == null) { + return "Error in expression - node has no kind"; + } + switch (kind) { + case Name: + if (Utilities.noString(name)) + return "No Name provided @ "+location(); + break; + + case Function: + if (function == null) + return "No Function id provided @ "+location(); + for (ExpressionNode n : parameters) { + String msg = n.check(); + if (msg != null) + return msg; + } + + break; + + case Unary: + break; + case Constant: + if (constant == null) + return "No Constant provided @ "+location(); + break; + + case Group: + if (group == null) + return "No Group provided @ "+location(); + else { + String msg = group.check(); + if (msg != null) + return msg; + } + } + if (inner != null) { + String msg = inner.check(); + if (msg != null) + return msg; + } + if (operation == null) { + + if (opNext != null) + return "Next provided when it shouldn't be @ "+location(); + } + else { + if (opNext == null) + return "No Next provided @ "+location(); + else + opNext.check(); + } + return null; + + } + + private String location() { + return Integer.toString(start.getLine())+", "+Integer.toString(start.getColumn()); + } + + public TypeDetails getTypes() { + return types; + } + + public void setTypes(TypeDetails types) { + this.types = types; + } + + public TypeDetails getOpTypes() { + return opTypes; + } + + public void setOpTypes(TypeDetails opTypes) { + this.opTypes = opTypes; + } + + public List getDistalNames() { + List names = new ArrayList(); if (operation != null) { - b.append(" "); - b.append(operation.toCode()); - b.append(" "); - b.append(opNext.toString()); - } - - return b.toString(); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Base getConstant() { - return constant; - } - - public void setConstant(Base constant) { - this.constant = constant; - } - - public Function getFunction() { - return function; - } - - public void setFunction(Function function) { - this.function = function; - if (parameters == null) - parameters = new ArrayList(); - } - - public boolean isProximal() { - return proximal; - } - - public void setProximal(boolean proximal) { - this.proximal = proximal; - } - - public Operation getOperation() { - return operation; - } - - public void setOperation(Operation operation) { - this.operation = operation; - } - - public ExpressionNode getInner() { - return inner; - } - - public void setInner(ExpressionNode value) { - this.inner = value; - } - - public ExpressionNode getOpNext() { - return opNext; - } - - public void setOpNext(ExpressionNode value) { - this.opNext = value; - } - - public List getParameters() { - return parameters; - } - - public boolean checkName() { - if (!name.startsWith("$")) - return true; - else - return Utilities.existsInList(name, "$this", "$total", "$index"); - } - - public Kind getKind() { - return kind; - } - - public void setKind(Kind kind) { - this.kind = kind; - } - - public ExpressionNode getGroup() { - return group; - } - - public void setGroup(ExpressionNode group) { - this.group = group; - } - - public SourceLocation getStart() { - return start; - } - - public void setStart(SourceLocation start) { - this.start = start; - } - - public SourceLocation getEnd() { - return end; - } - - public void setEnd(SourceLocation end) { - this.end = end; - } - - public SourceLocation getOpStart() { - return opStart; - } - - public void setOpStart(SourceLocation opStart) { - this.opStart = opStart; - } - - public SourceLocation getOpEnd() { - return opEnd; - } - - public void setOpEnd(SourceLocation opEnd) { - this.opEnd = opEnd; - } - - public String getUniqueId() { - return uniqueId; - } - - public int parameterCount() { - if (parameters == null) - return 0; - else - return parameters.size(); - } - - public String Canonical() { - StringBuilder b = new StringBuilder(); - write(b); - return b.toString(); - } - - public String summary() { - switch (kind) { - case Name: - return uniqueId + ": " + name; - case Function: - return uniqueId + ": " + function.toString() + "()"; - case Constant: - return uniqueId + ": " + constant; - case Group: - return uniqueId + ": (Group)"; - } - return "?exp-kind?"; - } - - private void write(StringBuilder b) { - - switch (kind) { - case Name: - b.append(name); - break; - case Constant: - b.append(constant); - break; - case Function: - b.append(function.toCode()); - b.append('('); - boolean f = true; - for (ExpressionNode n : parameters) { - if (f) - f = false; - else - b.append(", "); - n.write(b); - } - b.append(')'); - - break; - case Group: - b.append('('); - group.write(b); - b.append(')'); - } - - if (inner != null) { - b.append('.'); - inner.write(b); - } - if (operation != null) { - b.append(' '); - b.append(operation.toCode()); - b.append(' '); - opNext.write(b); - } - } - - public String check() { - - if (kind == null) { - return "Error in expression - node has no kind"; - } - switch (kind) { - case Name: - if (Utilities.noString(name)) - return "No Name provided @ " + location(); - break; - - case Function: - if (function == null) - return "No Function id provided @ " + location(); - for (ExpressionNode n : parameters) { - String msg = n.check(); - if (msg != null) - return msg; - } - - break; - - case Unary: - break; - case Constant: - if (constant == null) - return "No Constant provided @ " + location(); - break; - - case Group: - if (group == null) - return "No Group provided @ " + location(); - else { - String msg = group.check(); - if (msg != null) - return msg; - } - } - if (inner != null) { - String msg = inner.check(); - if (msg != null) - return msg; - } - if (operation == null) { - - if (opNext != null) - return "Next provided when it shouldn't be @ " + location(); + names.add(null); + } else if (inner != null) { + names.addAll(inner.getDistalNames()); + } else if (group != null) { + names.addAll(group.getDistalNames()); + } else if (function != null) { + names.add(null); + } else if (constant != null) { + names.add(null); } else { - if (opNext == null) - return "No Next provided @ " + location(); - else - opNext.check(); - } - return null; - + names.add(name); + } + return names; } - private String location() { - return Integer.toString(start.getLine()) + ", " + Integer.toString(start.getColumn()); + public boolean isNullSet() { + return kind == Kind.Constant && constant == null; } - - public TypeDetails getTypes() { - return types; - } - - public void setTypes(TypeDetails types) { - this.types = types; - } - - public TypeDetails getOpTypes() { - return opTypes; - } - - public void setOpTypes(TypeDetails opTypes) { - this.opTypes = opTypes; - } - + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRLexer.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRLexer.java index 3be115445..2ef4a114c 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRLexer.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRLexer.java @@ -1,6 +1,10 @@ package org.hl7.fhir.r4.fhirpath; +import java.util.ArrayList; +import java.util.List; + import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.SourceLocation; import org.hl7.fhir.utilities.Utilities; @@ -11,20 +15,23 @@ import org.hl7.fhir.utilities.Utilities; public class FHIRLexer { public class FHIRLexerException extends FHIRException { - public FHIRLexerException() { - super(); + private SourceLocation location; + + public FHIRLexerException(String message) { + super(message); } - + public FHIRLexerException(String message, Throwable cause) { super(message, cause); } - public FHIRLexerException(String message) { + public FHIRLexerException(String message, SourceLocation location) { super(message); + this.location = location; } - public FHIRLexerException(Throwable cause) { - super(cause); + public SourceLocation getLocation() { + return location; } } @@ -33,25 +40,44 @@ public class FHIRLexer { private int cursor; private int currentStart; private String current; + private List comments = new ArrayList<>(); private SourceLocation currentLocation; private SourceLocation currentStartLocation; private int id; private String name; + private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host + private SourceLocation commentLocation; + private boolean metadataFormat; + private boolean allowDoubleQuotes; public FHIRLexer(String source, String name) throws FHIRLexerException { - this.source = source; + this.source = source == null ? "" : Utilities.stripBOM(source); this.name = name == null ? "??" : name; currentLocation = new SourceLocation(1, 1); next(); } public FHIRLexer(String source, int i) throws FHIRLexerException { - this.source = source; + this.source = Utilities.stripBOM(source); this.cursor = i; currentLocation = new SourceLocation(1, 1); next(); } - + public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException { + this.source = Utilities.stripBOM(source); + this.cursor = i; + this.allowDoubleQuotes = allowDoubleQuotes; + currentLocation = new SourceLocation(1, 1); + next(); + } + public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException { + this.source = source == null ? "" : Utilities.stripBOM(source); + this.name = name == null ? "??" : name; + this.metadataFormat = metadataFormat; + this.allowDoubleQuotes = allowDoubleQuotes; + currentLocation = new SourceLocation(1, 1); + next(); + } public String getCurrent() { return current; } @@ -61,18 +87,15 @@ public class FHIRLexer { } public boolean isConstant() { - return current != null && (current.charAt(0) == '\'' || current.charAt(0) == '"') || current.charAt(0) == '@' - || current.charAt(0) == '%' || current.charAt(0) == '-' || current.charAt(0) == '+' - || (current.charAt(0) >= '0' && current.charAt(0) <= '9') || current.equals("true") || current.equals("false") - || current.equals("{}"); + return FHIRPathConstant.isFHIRPathConstant(current); } public boolean isFixedName() { - return current != null && (current.charAt(0) == '`'); + return FHIRPathConstant.isFHIRPathFixedName(current); } public boolean isStringConstant() { - return current.charAt(0) == '\'' || current.charAt(0) == '"' || current.charAt(0) == '`'; + return FHIRPathConstant.isFHIRPathStringConstant(current); } public String take() throws FHIRLexerException { @@ -84,7 +107,7 @@ public class FHIRLexer { public int takeInt() throws FHIRLexerException { String s = current; if (!Utilities.isInteger(s)) - throw error("Found " + current + " expecting an integer"); + throw error("Found "+current+" expecting an integer"); next(); return Integer.parseInt(s); } @@ -99,12 +122,10 @@ public class FHIRLexer { if (current.equals("*") || current.equals("**")) return true; - if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') - || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) { - for (int i = 1; i < current.length(); i++) - if (!((current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') - || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') - || (current.charAt(1) >= '0' && current.charAt(1) <= '9'))) + if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) { + for (int i = 1; i < current.length(); i++) + if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') || + (current.charAt(1) >= '0' && current.charAt(1) <= '9'))) return false; return true; } @@ -112,11 +133,11 @@ public class FHIRLexer { } public FHIRLexerException error(String msg) { - return error(msg, currentLocation.toString()); + return error(msg, currentLocation.toString(), currentLocation); } - public FHIRLexerException error(String msg, String location) { - return new FHIRLexerException("Error in " + name + " at " + location + ": " + msg); + public FHIRLexerException error(String msg, String location, SourceLocation loc) { + return new FHIRLexerException("Error @"+location+": "+msg, loc); } public void next() throws FHIRLexerException { @@ -126,34 +147,30 @@ public class FHIRLexer { currentStartLocation = currentLocation; if (cursor < source.length()) { char ch = source.charAt(cursor); - if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') { + if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') { cursor++; - if (cursor < source.length() - && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-') - || (ch == '-' && source.charAt(cursor) == '>')) + if (cursor < source.length() && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-') || (ch == '-' && source.charAt(cursor) == '>')) cursor++; current = source.substring(currentStart, cursor); - } else if (ch == '.') { + } else if (ch == '.' ) { cursor++; - if (cursor < source.length() && (source.charAt(cursor) == '.')) + if (cursor < source.length() && (source.charAt(cursor) == '.')) cursor++; current = source.substring(currentStart, cursor); } else if (ch >= '0' && ch <= '9') { - cursor++; + cursor++; boolean dotted = false; - while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') - || (source.charAt(cursor) == '.') && !dotted)) { + while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || (source.charAt(cursor) == '.') && !dotted)) { if (source.charAt(cursor) == '.') dotted = true; cursor++; } - if (source.charAt(cursor - 1) == '.') + if (source.charAt(cursor-1) == '.') cursor--; current = source.substring(currentStart, cursor); - } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { - while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') - || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') - || (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_')) + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || + (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_')) cursor++; current = source.substring(currentStart, cursor); } else if (ch == '%') { @@ -164,19 +181,20 @@ public class FHIRLexer { cursor++; cursor++; } else - while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') - || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') - || (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' - || source.charAt(cursor) == '-')) - cursor++; + while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || + (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_')) + cursor++; current = source.substring(currentStart, cursor); } else if (ch == '/') { cursor++; if (cursor < source.length() && (source.charAt(cursor) == '/')) { - // this is en error - should already have been skipped - error("This shoudn't happen?"); + // we've run into metadata + cursor++; + cursor++; + current = source.substring(currentStart, cursor); + } else { + current = source.substring(currentStart, cursor); } - current = source.substring(currentStart, cursor); } else if (ch == '$') { cursor++; while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')) @@ -188,42 +206,42 @@ public class FHIRLexer { if (ch == '}') cursor++; current = source.substring(currentStart, cursor); - } else if (ch == '"') { + } else if (ch == '"' && allowDoubleQuotes) { cursor++; boolean escape = false; while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) { if (escape) escape = false; - else + else escape = (source.charAt(cursor) == '\\'); cursor++; } if (cursor == source.length()) throw error("Unterminated string"); cursor++; - current = "\"" + source.substring(currentStart + 1, cursor - 1) + "\""; + current = "\""+source.substring(currentStart+1, cursor-1)+"\""; } else if (ch == '`') { cursor++; boolean escape = false; while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) { if (escape) escape = false; - else + else escape = (source.charAt(cursor) == '\\'); cursor++; } if (cursor == source.length()) throw error("Unterminated string"); cursor++; - current = "`" + source.substring(currentStart + 1, cursor - 1) + "`"; - } else if (ch == '\'') { + current = "`"+source.substring(currentStart+1, cursor-1)+"`"; + } else if (ch == '\''){ cursor++; char ech = ch; boolean escape = false; while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) { if (escape) escape = false; - else + else escape = (source.charAt(cursor) == '\\'); cursor++; } @@ -232,26 +250,32 @@ public class FHIRLexer { cursor++; current = source.substring(currentStart, cursor); if (ech == '\'') - current = "\'" + current.substring(1, current.length() - 1) + "\'"; + current = "\'"+current.substring(1, current.length() - 1)+"\'"; } else if (ch == '`') { cursor++; boolean escape = false; while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) { if (escape) escape = false; - else + else escape = (source.charAt(cursor) == '\\'); cursor++; } if (cursor == source.length()) throw error("Unterminated string"); cursor++; - current = "`" + source.substring(currentStart + 1, cursor - 1) + "`"; - } else if (ch == '@') { + current = "`"+source.substring(currentStart+1, cursor-1)+"`"; + } else if (ch == '|' && liquidMode) { + cursor++; + ch = source.charAt(cursor); + if (ch == '|') + cursor++; + current = source.substring(currentStart, cursor); + } else if (ch == '@'){ int start = cursor; cursor++; while (cursor < source.length() && isDateChar(source.charAt(cursor), start)) - cursor++; + cursor++; current = source.substring(currentStart, cursor); } else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then cursor++; @@ -261,23 +285,36 @@ public class FHIRLexer { } private void skipWhitespaceAndComments() { + comments.clear(); + commentLocation = null; boolean last13 = false; boolean done = false; while (cursor < source.length() && !done) { - if (cursor < source.length() - 1 && "//".equals(source.substring(cursor, cursor + 2))) { - while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) - cursor++; - } else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor + 2))) { - while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor + 2))) { - last13 = currentLocation.checkChar(source.charAt(cursor), last13); - cursor++; + if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2)) && !isMetadataStart()) { + if (commentLocation == null) { + commentLocation = currentLocation.copy(); } - if (cursor >= source.length() - 1) { + int start = cursor+2; + while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) { + cursor++; + } + comments.add(source.substring(start, cursor).trim()); + } else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) { + if (commentLocation == null) { + commentLocation = currentLocation.copy(); + } + int start = cursor+2; + while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) { + last13 = currentLocation.checkChar(source.charAt(cursor), last13); + cursor++; + } + if (cursor >= source.length() -1) { error("Unfinished comment"); } else { + comments.add(source.substring(start, cursor).trim()); cursor = cursor + 2; } - } else if (Character.isWhitespace(source.charAt(cursor))) { + } else if (Utilities.isWhitespace(source.charAt(cursor))) { last13 = currentLocation.checkChar(source.charAt(cursor), last13); cursor++; } else { @@ -285,13 +322,15 @@ public class FHIRLexer { } } } - - private boolean isDateChar(char ch, int start) { - int eot = source.charAt(start + 1) == 'T' ? 10 : 20; - - return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch) - || (cursor - start == eot && ch == '.' && cursor < source.length() - 1 - && Character.isDigit(source.charAt(cursor + 1))); + + private boolean isMetadataStart() { + return metadataFormat && cursor < source.length() - 2 && "///".equals(source.substring(cursor, cursor+3)); + } + + private boolean isDateChar(char ch,int start) { + int eot = source.charAt(start+1) == 'T' ? 10 : 20; + + return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch) || (cursor-start == eot && ch == '.' && cursor < source.length()-1&& Character.isDigit(source.charAt(cursor+1))); } public boolean isOp() { @@ -320,35 +359,60 @@ public class FHIRLexer { return !done() && current.startsWith("//"); } + public boolean hasComments() { + return comments.size() > 0; + } + + + public List getComments() { + return comments; + } + + public String getAllComments() { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("\r\n"); + b.addAll(comments); + comments.clear(); + return b.toString(); + } + + public String getFirstComment() { + if (hasComments()) { + String s = comments.get(0); + comments.remove(0); + return s; + } else { + return null; + } + } + public boolean hasToken(String kw) { return !done() && kw.equals(current); } - public boolean hasToken(String... names) { - if (done()) + if (done()) return false; for (String s : names) if (s.equals(current)) return true; return false; } - + public void token(String kw) throws FHIRLexerException { - if (!kw.equals(current)) - throw error("Found \"" + current + "\" expecting \"" + kw + "\""); + if (!kw.equals(current)) + throw error("Found \""+current+"\" expecting \""+kw+"\""); next(); } - + public String readConstant(String desc) throws FHIRLexerException { if (!isStringConstant()) - throw error("Found " + current + " expecting \"[" + desc + "]\""); + throw error("Found "+current+" expecting \"["+desc+"]\""); return processConstant(take()); } public String readFixedName(String desc) throws FHIRLexerException { if (!isFixedName()) - throw error("Found " + current + " expecting \"[" + desc + "]\""); + throw error("Found "+current+" expecting \"["+desc+"]\""); return processFixedName(take()); } @@ -356,21 +420,21 @@ public class FHIRLexer { public String processConstant(String s) throws FHIRLexerException { StringBuilder b = new StringBuilder(); int i = 1; - while (i < s.length() - 1) { + while (i < s.length()-1) { char ch = s.charAt(i); if (ch == '\\') { i++; switch (s.charAt(i)) { - case 't': + case 't': b.append('\t'); break; case 'r': b.append('\r'); break; - case 'n': + case 'n': b.append('\n'); break; - case 'f': + case 'f': b.append('\f'); break; case '\'': @@ -382,20 +446,20 @@ public class FHIRLexer { case '`': b.append('`'); break; - case '\\': + case '\\': b.append('\\'); break; - case '/': + case '/': b.append('/'); break; case 'u': i++; - int uc = Integer.parseInt(s.substring(i, i + 4), 16); + int uc = Integer.parseInt(s.substring(i, i+4), 16); b.append(Character.toString(uc)); i = i + 4; break; default: - throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i)); + throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation); } } else { b.append(ch); @@ -404,25 +468,25 @@ public class FHIRLexer { } return b.toString(); } - + public String processFixedName(String s) throws FHIRLexerException { StringBuilder b = new StringBuilder(); int i = 1; - while (i < s.length() - 1) { + while (i < s.length()-1) { char ch = s.charAt(i); if (ch == '\\') { i++; switch (s.charAt(i)) { - case 't': + case 't': b.append('\t'); break; case 'r': b.append('\r'); break; - case 'n': + case 'n': b.append('\n'); break; - case 'f': + case 'f': b.append('\f'); break; case '\'': @@ -431,20 +495,20 @@ public class FHIRLexer { case '"': b.append('"'); break; - case '\\': + case '\\': b.append('\\'); break; - case '/': + case '/': b.append('/'); break; case 'u': i++; - int uc = Integer.parseInt(s.substring(i, i + 4), 16); + int uc = Integer.parseInt(s.substring(i, i+4), 32); b.append(Character.toString(uc)); i = i + 4; break; default: - throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i)); + throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation); } } else { b.append(ch); @@ -457,9 +521,9 @@ public class FHIRLexer { public void skipToken(String token) throws FHIRLexerException { if (getCurrent().equals(token)) next(); - + } - + public String takeDottedToken() throws FHIRLexerException { StringBuilder b = new StringBuilder(); b.append(take()); @@ -478,5 +542,39 @@ public class FHIRLexer { public int getCurrentStart() { return currentStart; } - + public String getSource() { + return source; + } + public boolean isLiquidMode() { + return liquidMode; + } + public void setLiquidMode(boolean liquidMode) { + this.liquidMode = liquidMode; + } + public SourceLocation getCommentLocation() { + return this.commentLocation; + } + public boolean isMetadataFormat() { + return metadataFormat; + } + public void setMetadataFormat(boolean metadataFormat) { + this.metadataFormat = metadataFormat; + } + public List cloneComments() { + List res = new ArrayList<>(); + res.addAll(getComments()); + return res; + } + public String tokenWithTrailingComment(String token) { + int line = getCurrentLocation().getLine(); + token(token); + if (getComments().size() > 0 && getCommentLocation().getLine() == line) { + return getFirstComment(); + } else { + return null; + } + } + public boolean isAllowDoubleQuotes() { + return allowDoubleQuotes; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java index 3ca6de5b9..a6953bd5e 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/FHIRPathEngine.java @@ -24,14 +24,15 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.conformance.ProfileUtilities; +import org.hl7.fhir.r4.context.ContextUtilities; import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; -import org.hl7.fhir.r4.fhirpath.FHIRPathEngine; import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; import org.hl7.fhir.r4.fhirpath.ExpressionNode.Function; import org.hl7.fhir.r4.fhirpath.ExpressionNode.Kind; import org.hl7.fhir.r4.fhirpath.ExpressionNode.Operation; import org.hl7.fhir.r4.fhirpath.FHIRLexer.FHIRLexerException; +import org.hl7.fhir.r4.fhirpath.FHIRPathEngine.ExtensionDefinition; import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.ClassTypeInfo; import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FHIRConstant; import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FunctionDetails; @@ -40,6 +41,8 @@ import org.hl7.fhir.r4.fhirpath.TypeDetails.ProfiledType; import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.BaseDateTimeType; import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Constants; import org.hl7.fhir.r4.model.DateTimeType; @@ -48,6 +51,7 @@ import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Element; import org.hl7.fhir.r4.model.ElementDefinition; import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.Property; import org.hl7.fhir.r4.model.Property.PropertyMatcher; @@ -61,6 +65,7 @@ import org.hl7.fhir.r4.model.TimeType; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.FhirPublication; +import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.MergedList; import org.hl7.fhir.utilities.MergedList.MergeNode; import org.hl7.fhir.utilities.SourceLocation; @@ -103,6 +108,7 @@ import ca.uhn.fhir.util.ElementUtil; */ + /** * * @author Grahame Grieve @@ -110,49 +116,90 @@ import ca.uhn.fhir.util.ElementUtil; */ public class FHIRPathEngine { - private enum Equality { - Null, True, False + public class ExtensionDefinition { + + private boolean root; + private StructureDefinition sd; + private ElementDefinition ed; + + public ExtensionDefinition(boolean root, StructureDefinition sd, ElementDefinition ed) { + super(); + this.root = root; + this.sd = sd; + this.ed = ed; + } + + public boolean isRoot() { + return root; + } + + public StructureDefinition getSd() { + return sd; + } + + public ElementDefinition getEd() { + return ed; + } + } + + public class IssueMessage { + + private String message; + private String id; + + public IssueMessage(String message, String id) { + this.message = message; + this.id = id; + } + + public String getMessage() { + return message; + } + + public String getId() { + return id; + } + + } + + private enum Equality { Null, True, False } + private IWorkerContext worker; private IEvaluationContext hostServices; private StringBuilder log = new StringBuilder(); private Set primitiveTypes = new HashSet(); private Map allTypes = new HashMap(); - private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for emptty sets, so when - // running for R2/R3, this is set ot true + private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for empty sets, so when running for R2/R3, this is set ot true private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R4); private ProfileUtilities profileUtilities; private String location; // for error messages private boolean allowPolymorphicNames; private boolean doImplicitStringConversion; - private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the - // host + private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host private boolean doNotEnforceAsSingletonRule; private boolean doNotEnforceAsCaseSensitive; + private boolean allowDoubleQuotes; + private List typeWarnings = new ArrayList<>(); + private boolean emitSQLonFHIRWarning; - // if the fhir path expressions are allowed to use constants beyond those - // defined in the specification - // the application can implement them by providing a constant resolver + // if the fhir path expressions are allowed to use constants beyond those defined in the specification + // the application can implement them by providing a constant resolver public interface IEvaluationContext { - /** - * A constant reference - e.g. a reference to a name that must be resolved in - * context. The % will be removed from the constant name before this is invoked. + * A constant reference - e.g. a reference to a name that must be resolved in context. + * The % will be removed from the constant name before this is invoked. * Variables created with defineVariable will not be processed by resolveConstant (or resolveConstantType) * - * This will also be called if the host invokes the FluentPath engine with a - * context of null - * - * @param appContext - content passed into the fluent path engine - * @param name - name reference to resolve - * @param beforeContext - whether this is being called before the name is - * resolved locally, or not - * @return the value of the reference (or null, if it's not valid, though can - * throw an exception if desired) + * This will also be called if the host invokes the FluentPath engine with a context of null + * + * @param appContext - content passed into the fluent path engine + * @param name - name reference to resolve + * @param beforeContext - whether this is being called before the name is resolved locally, or not + * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired) */ - public List resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException; - + public List resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException; public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException; /** @@ -173,15 +220,12 @@ public class FHIRPathEngine { public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName); /** - * Check the function parameters, and throw an error if they are incorrect, or - * return the type for the function - * + * Check the function parameters, and throw an error if they are incorrect, or return the type for the function * @param functionName * @param parameters * @return */ - public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List parameters) - throws PathEngineException; + public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List parameters) throws PathEngineException; /** * @param appContext @@ -189,31 +233,34 @@ public class FHIRPathEngine { * @param parameters * @return */ - public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, - List> parameters); + public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters); /** - * Implementation of resolve() function. Passed a string, return matching - * resource, if one is known - else null - * + * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null * @appContext - passed in by the host to the FHIRPathEngine * @param url the reference (Reference.reference or the value of the canonical * @return - * @throws FHIRException + * @throws FHIRException */ public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException; public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException; - /* + /* * return the value set referenced by the url, which has been used in memberOf() */ public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url); + + /** + * For the moment, there can only be one parameter if it's a type parameter + * @param name + * @return true if it's a type parameter + */ + public boolean paramIsType(String name, int index); } /** - * @param worker - used when validating paths (@check), and used doing value set - * membership when executing tests (once that's defined) + * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) */ public FHIRPathEngine(IWorkerContext worker) { this(worker, new ProfileUtilities(worker, null, null)); @@ -222,17 +269,17 @@ public class FHIRPathEngine { public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) { super(); this.worker = worker; - profileUtilities = utilities; - for (StructureDefinition sd : worker.allStructures()) { + profileUtilities = utilities; + for (StructureDefinition sd : worker.fetchResourcesByType(StructureDefinition.class)) { if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { allTypes.put(sd.getName(), sd); } - if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION - && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { primitiveTypes.add(sd.getName()); } } initFlags(); + cu = new ContextUtilities(worker); } private void initFlags() { @@ -242,16 +289,15 @@ public class FHIRPathEngine { } } - // --- 3 methods to override in children - // ------------------------------------------------------- - // if you don't override, it falls through to the using the base reference - // implementation + // --- 3 methods to override in children ------------------------------------------------------- + // if you don't override, it falls through to the using the base reference implementation // HAPI overrides to these to support extending the base model public IEvaluationContext getHostServices() { return hostServices; } + public void setHostServices(IEvaluationContext constantResolver) { this.hostServices = constantResolver; } @@ -260,34 +306,37 @@ public class FHIRPathEngine { return location; } + public void setLocation(String location) { this.location = location; } + /** - * Given an item, return all the children that conform to the pattern described - * in name - * - * Possible patterns: - a simple name (which may be the base of a name with [] - * e.g. value[x]) - a name with a type replacement e.g. valueCodeableConcept - * - * which means all children - ** which means all descendants + * Given an item, return all the children that conform to the pattern described in name * + * Possible patterns: + * - a simple name (which may be the base of a name with [] e.g. value[x]) + * - a name with a type replacement e.g. valueCodeableConcept + * - * which means all children + * - ** which means all descendants + * * @param item * @param name * @param result - * @throws FHIRException + * @throws FHIRException */ protected void getChildrenByName(Base item, String name, List result) throws FHIRException { String tn = null; if (isAllowPolymorphicNames()) { - // we'll look to see whether we hav a polymorphic name + // we'll look to see whether we have a polymorphic name for (Property p : item.children()) { if (p.getName().endsWith("[x]")) { - String n = p.getName().substring(0, p.getName().length() - 3); + String n = p.getName().substring(0, p.getName().length()-3); if (name.startsWith(n)) { tn = name.substring(n.length()); name = n; - break; + break; } } } @@ -308,15 +357,16 @@ public class FHIRPathEngine { } return v; } - public boolean isLegacyMode() { return legacyMode; } + public void setLegacyMode(boolean legacyMode) { this.legacyMode = legacyMode; } + public boolean isDoImplicitStringConversion() { return doImplicitStringConversion; } @@ -347,7 +397,7 @@ public class FHIRPathEngine { * * @param path * @return - * @throws PathEngineException + * @throws PathEngineException * @throws Exception */ public ExpressionNode parse(String path) throws FHIRLexerException { @@ -355,188 +405,303 @@ public class FHIRPathEngine { } public ExpressionNode parse(String path, String name) throws FHIRLexerException { - FHIRLexer lexer = new FHIRLexer(path, name); + FHIRLexer lexer = new FHIRLexer(path, name, false, allowDoubleQuotes); if (lexer.done()) { throw lexer.error("Path cannot be empty"); } ExpressionNode result = parseExpression(lexer, true); if (!lexer.done()) { - throw lexer.error("Premature ExpressionNode termination at unexpected token \"" + lexer.getCurrent() + "\""); + throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); } result.check(); - return result; + return result; } public static class ExpressionNodeWithOffset { private int offset; private ExpressionNode node; - public ExpressionNodeWithOffset(int offset, ExpressionNode node) { super(); this.offset = offset; this.node = node; } - public int getOffset() { return offset; } - public ExpressionNode getNode() { return node; } } - /** * Parse a path for later use using execute * * @param path * @return - * @throws PathEngineException + * @throws PathEngineException * @throws Exception */ public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { - FHIRLexer lexer = new FHIRLexer(path, i); + FHIRLexer lexer = new FHIRLexer(path, i, allowDoubleQuotes); if (lexer.done()) { throw lexer.error("Path cannot be empty"); } ExpressionNode result = parseExpression(lexer, true); result.check(); - return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); + return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); } /** * Parse a path that is part of some other syntax - * + * * @return - * @throws PathEngineException + * @throws PathEngineException * @throws Exception */ public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { ExpressionNode result = parseExpression(lexer, true); result.check(); - return result; + return result; } /** * check that paths referred to in the ExpressionNode are valid * - * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path - * sometimes needs a different starting point than the xpath + * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath * - * returns a list of the possible types that might be returned by executing the - * ExpressionNode against a particular context + * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context * * @param context - the logical type against which this path is applied * @throws DefinitionException - * @throws PathEngineException + * @throws PathEngineException * @if the path is not valid */ - public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) - throws FHIRLexerException, PathEngineException, DefinitionException { - // if context is a path that refers to a type, do that conversion now - TypeDetails types; + public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { + return check(appContext, resourceType, context, expr, null); + } + + /** + * check that paths referred to in the ExpressionNode are valid + * + * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath + * + * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context + * + * @param context - the logical type against which this path is applied + * @throws DefinitionException + * @throws PathEngineException + * @if the path is not valid + */ + public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr, Set elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException { + + // if context is a path that refers to a type, do that conversion now + TypeDetails types; if (context == null) { - types = null; // this is a special case; the first path reference will have to resolve to - // something in the context + types = null; // this is a special case; the first path reference will have to resolve to something in the context } else if (!context.contains(".")) { StructureDefinition sd = worker.fetchTypeDefinition(context); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context); + } types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); } else { String ctxt = context.substring(0, context.indexOf('.')); if (Utilities.isAbsoluteUrl(resourceType)) { - ctxt = resourceType.substring(0, resourceType.lastIndexOf("/") + 1) + ctxt; + ctxt = resourceType; //.substring(0, resourceType.lastIndexOf("/")+1)+ctxt; } - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); + StructureDefinition sd = cu.findType(ctxt); if (sd == null) { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context); } - ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr); - if (ed == null) { + List edl = getElementDefinition(sd, context, true, expr); + if (edl.size() == 0) { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); } - if (ed.fixedType != null) { + if (edl.size() > 1) { + throw new Error("Not handled here yet"); + } + ElementDefinitionMatch ed = edl.get(0); + if (ed.fixedType != null) { types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); - } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { - types = new TypeDetails(CollectionStatus.SINGLETON, ctxt + "#" + context); + } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { + types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context); } else { types = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent t : ed.getDefinition().getType()) { + for (TypeRefComponent t : ed.getDefinition().getType()) { types.addType(t.getCode()); } } } - return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true); + return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, elementDependencies, true, false, expr); + } + + /** + * check that paths referred to in the ExpressionNode are valid + * + * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath + * + * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context + * + * @param context - the logical type against which this path is applied + * @throws DefinitionException + * @throws PathEngineException + * @if the path is not valid + */ + public TypeDetails checkOnTypes(Object appContext, String resourceType, List typeList, ExpressionNode expr, List warnings) throws FHIRLexerException, PathEngineException, DefinitionException { + typeWarnings.clear(); + + // if context is a path that refers to a type, do that conversion now + TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON); + for (String t : typeList) { + if (!t.contains(".")) { + StructureDefinition sd = worker.fetchTypeDefinition(t); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); + } + types.addType(sd.getUrl()); + } else { + boolean checkTypeName = false; + String ctxt = null; + if (t.contains("#")) { + ctxt = t.substring(0, t.indexOf('#')); + t = t.substring(t.indexOf('#')+1); + } else if (Utilities.isAbsoluteUrl(t)) { + ctxt = t; + t = ctxt.substring(ctxt.lastIndexOf("/")+1); + checkTypeName = true; + } else { + ctxt = t.substring(0, t.indexOf('.')); + } + StructureDefinition sd = cu.findType(ctxt); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); + } + String tn = checkTypeName ? sd.getSnapshot().getElementFirstRep().getPath() : t; + + List edl = getElementDefinition(sd, tn, true, expr); + if (edl.size() == 0) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t); + } + if (edl.size() > 1) { + throw new Error("not handled here either"); + } + ElementDefinitionMatch ed = edl.get(0); + if (ed.fixedType != null) { + types.addType(ed.fixedType); + } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { + types.addType(sd.getType()+"#"+t); + } else { + for (TypeRefComponent tt : ed.getDefinition().getType()) { + types.addType(tt.getCode()); + } + } + } + } + TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr); + warnings.addAll(typeWarnings); + return res; + } + + public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List warnings) throws FHIRLexerException, PathEngineException, DefinitionException { + typeWarnings.clear(); + TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr); + warnings.addAll(typeWarnings); + return res; + } + + /** + * check that paths referred to in the ExpressionNode are valid + * + * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath + * + * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context + * + * @throws DefinitionException + * @throws PathEngineException + * @if the path is not valid + */ + public TypeDetails check(Object appContext, String resourceType, List resourceTypes, ExpressionNode expr, Set elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException { + + // if context is a path that refers to a type, do that conversion now + TypeDetails types = null; + for (String rt : resourceTypes) { + if (types == null) { + types = new TypeDetails(CollectionStatus.SINGLETON, rt); + } else { + types.addType(rt); + } + } + + return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, elementDependencies, true, false, expr); } private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) { String fmt = worker.formatMessagePlural(num, constName, args); if (location != null) { - fmt = fmt + " " + worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); + fmt = fmt + " "+worker.formatMessagePlural(num, I18nConstants.FHIRPATH_LOCATION, location); } - if (holder != null) { - return new PathEngineException(fmt, holder.getStart(), holder.toString()); + if (holder != null) { + return new PathEngineException(fmt, constName, holder.getStart(), holder.toString()); } else { - return new PathEngineException(fmt); + return new PathEngineException(fmt, constName); } } - + private FHIRException makeException(ExpressionNode holder, String constName, Object... args) { String fmt = worker.formatMessage(constName, args); if (location != null) { - fmt = fmt + " " + worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); + fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); } - if (holder != null) { - return new PathEngineException(fmt, holder.getStart(), holder.toString()); + if (holder != null) { + return new PathEngineException(fmt, constName, holder.getStart(), holder.toString()); } else { - return new PathEngineException(fmt); + return new PathEngineException(fmt, constName); } } - public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) - throws FHIRLexerException, PathEngineException, DefinitionException { - // if context is a path that refers to a type, do that conversion now - TypeDetails types; + public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { + // if context is a path that refers to a type, do that conversion now + TypeDetails types; if (!context.contains(".")) { types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); } else { - ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr); - if (ed == null) { + List edl = getElementDefinition(sd, context, true, expr); + if (edl.size() == 0) { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); } - if (ed.fixedType != null) { + if (edl.size() > 1) { + throw new Error("Unhandled case?"); + } + ElementDefinitionMatch ed = edl.get(0); + if (ed.fixedType != null) { types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); - } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { - types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl() + "#" + context); + } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { + types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); } else { types = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent t : ed.getDefinition().getType()) { + for (TypeRefComponent t : ed.getDefinition().getType()) { types.addType(t.getCode()); } } } - return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true); + return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, null, true, false, expr); } - public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) - throws FHIRLexerException, PathEngineException, DefinitionException { - // if context is a path that refers to a type, do that conversion now - TypeDetails types = null; // this is a special case; the first path reference will have to resolve to - // something in the context - return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, - true); + public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { + // if context is a path that refers to a type, do that conversion now + TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context + return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, null, true, false, expr); } - public TypeDetails check(Object appContext, String resourceType, String context, String expr) - throws FHIRLexerException, PathEngineException, DefinitionException { + public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { return check(appContext, resourceType, context, parse(expr)); } private Integer compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { - DateTimeType left = theL instanceof DateTimeType ? (DateTimeType) theL : new DateTimeType(theL.primitiveValue()); - DateTimeType right = theR instanceof DateTimeType ? (DateTimeType) theR : new DateTimeType(theR.primitiveValue()); + DateTimeType left = theL instanceof DateTimeType ? (DateTimeType) theL : new DateTimeType(theL.primitiveValue()); + DateTimeType right = theR instanceof DateTimeType ? (DateTimeType) theR : new DateTimeType(theR.primitiveValue()); if (theEquivalenceTest) { return left.equalsUsingFhirPathRules(right) == Boolean.TRUE ? 0 : 1; @@ -552,31 +717,27 @@ public class FHIRPathEngine { } private Integer compareTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { - TimeType left = theL instanceof TimeType ? (TimeType) theL : new TimeType(theL.primitiveValue()); - TimeType right = theR instanceof TimeType ? (TimeType) theR : new TimeType(theR.primitiveValue()); + TimeType left = theL instanceof TimeType ? (TimeType) theL : new TimeType(theL.primitiveValue()); + TimeType right = theR instanceof TimeType ? (TimeType) theR : new TimeType(theR.primitiveValue()); if (left.getHour() < right.getHour()) { return -1; } else if (left.getHour() > right.getHour()) { return 1; - // hour is not a valid precision - // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.YEAR && - // dateRight.getPrecision() == TemporalPrecisionEnum.YEAR) { - // return 0; - // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.HOUR || - // dateRight.getPrecision() == TemporalPrecisionEnum.HOUR) { - // return null; + // hour is not a valid precision + // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.YEAR && dateRight.getPrecision() == TemporalPrecisionEnum.YEAR) { + // return 0; + // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.HOUR || dateRight.getPrecision() == TemporalPrecisionEnum.HOUR) { + // return null; } if (left.getMinute() < right.getMinute()) { return -1; } else if (left.getMinute() > right.getMinute()) { return 1; - } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE - && right.getPrecision() == TemporalPrecisionEnum.MINUTE) { + } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE && right.getPrecision() == TemporalPrecisionEnum.MINUTE) { return 0; - } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE - || right.getPrecision() == TemporalPrecisionEnum.MINUTE) { + } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) { return null; } @@ -590,13 +751,15 @@ public class FHIRPathEngine { } + /** * evaluate a path and return the matching elements * - * @param base - the object against which the path is being evaluated + * @param base - the object against which the path is being evaluated * @param ExpressionNode - the parsed ExpressionNode statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ public List evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { List list = new ArrayList(); @@ -604,17 +767,36 @@ public class FHIRPathEngine { list.add(base); } log = new StringBuilder(); - return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, - base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); + return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); } + + /** + * evaluate a path and return the matching elements + * + * @param base - the object against which the path is being evaluated + * @param ExpressionNode - the parsed ExpressionNode statement to use + * @return + * @throws FHIRException + * @ + */ + public List evaluate(Object appContext, Base base, ExpressionNode ExpressionNode) throws FHIRException { + List list = new ArrayList(); + if (base != null) { + list.add(base); + } + log = new StringBuilder(); + return execute(new ExecutionContext(appContext, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); + } + /** * evaluate a path and return the matching elements * * @param base - the object against which the path is being evaluated * @param path - the FHIR Path statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ public List evaluate(Base base, String path) throws FHIRException { ExpressionNode exp = parse(path); @@ -623,47 +805,43 @@ public class FHIRPathEngine { list.add(base); } log = new StringBuilder(); - return execute( - new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base), - list, exp, true); + return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base), list, exp, true); } /** * evaluate a path and return the matching elements * - * @param base - the object against which the path is being evaluated + * @param base - the object against which the path is being evaluated * @param ExpressionNode - the parsed ExpressionNode statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public List evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, - ExpressionNode ExpressionNode) throws FHIRException { + public List evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException { List list = new ArrayList(); if (base != null) { list.add(base); } log = new StringBuilder(); - return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, - ExpressionNode, true); + return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, ExpressionNode, true); } /** * evaluate a path and return the matching elements * - * @param base - the object against which the path is being evaluated + * @param base - the object against which the path is being evaluated * @param expressionNode - the parsed ExpressionNode statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public List evaluate(Object appContext, Base focusResource, Base rootResource, Base base, - ExpressionNode expressionNode) throws FHIRException { + public List evaluate(Object appContext, Base focusResource, Base rootResource, Base base, ExpressionNode expressionNode) throws FHIRException { List list = new ArrayList(); if (base != null) { list.add(base); } log = new StringBuilder(); - return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, - expressionNode, true); + return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, expressionNode, true); } /** @@ -672,10 +850,10 @@ public class FHIRPathEngine { * @param base - the object against which the path is being evaluated * @param path - the FHIR Path statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public List evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) - throws FHIRException { + public List evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { ExpressionNode exp = parse(path); List list = new ArrayList(); if (base != null) { @@ -691,10 +869,10 @@ public class FHIRPathEngine { * @param base - the object against which the path is being evaluated * @param path - the FHIR Path statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) - throws FHIRException { + public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { return convertToBoolean(evaluate(null, focusResource, rootResource, base, path)); } @@ -703,10 +881,10 @@ public class FHIRPathEngine { * * @param base - the object against which the path is being evaluated * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) - throws FHIRException { + public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { return convertToBoolean(evaluate(null, focusResource, rootResource, base, node)); } @@ -714,12 +892,12 @@ public class FHIRPathEngine { * evaluate a path and return true or false (e.g. for an invariant) * * @param appInfo - application context - * @param base - the object against which the path is being evaluated + * @param base - the object against which the path is being evaluated * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, - ExpressionNode node) throws FHIRException { + public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); } @@ -728,10 +906,10 @@ public class FHIRPathEngine { * * @param base - the object against which the path is being evaluated * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ - public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, - ExpressionNode node) throws FHIRException { + public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); } @@ -741,14 +919,14 @@ public class FHIRPathEngine { * @param base - the object against which the path is being evaluated * @param path - the FHIR Path statement to use * @return - * @throws FHIRException @ + * @throws FHIRException + * @ */ public String evaluateToString(Base base, String path) throws FHIRException { return convertToString(evaluate(base, path)); } - public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) - throws FHIRException { + public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { return convertToString(evaluate(appInfo, focusResource, rootResource, base, node)); } @@ -762,7 +940,7 @@ public class FHIRPathEngine { StringBuilder b = new StringBuilder(); boolean first = true; for (Base item : items) { - if (first) { + if (first) { first = false; } else { b.append(','); @@ -775,20 +953,18 @@ public class FHIRPathEngine { public String convertToString(Base item) { if (item instanceof IIdType) { - return ((IIdType) item).getIdPart(); + return ((IIdType)item).getIdPart(); } else if (item.isPrimitive()) { return item.primitiveValue(); } else if (item instanceof Quantity) { Quantity q = (Quantity) item; - if (q.hasUnit() - && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", - "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds") + if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds") && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) { - return q.getValue().toPlainString() + " " + q.getUnit(); + return q.getValue().toPlainString()+" "+q.getUnit(); } if (q.getSystem().equals("http://unitsofmeasure.org")) { - String u = "'" + q.getCode() + "'"; - return q.getValue().toPlainString() + " " + u; + String u = "'"+q.getCode()+"'"; + return q.getValue().toPlainString()+" "+u; } else { return item.toString(); } @@ -797,8 +973,7 @@ public class FHIRPathEngine { } /** - * worker routine for converting a set of objects to a boolean representation - * (for invariants) + * worker routine for converting a set of objects to a boolean representation (for invariants) * * @param items - result from @evaluate * @return @@ -810,11 +985,12 @@ public class FHIRPathEngine { return ((BooleanType) items.get(0)).getValue(); } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { // element model return Boolean.valueOf(items.get(0).primitiveValue()); - } else { + } else { return items.size() > 0; } } + private void log(String name, List contents) { if (hostServices == null || !hostServices.log(name, contents)) { if (log.length() > 0) { @@ -836,7 +1012,7 @@ public class FHIRPathEngine { public String forLog() { if (log.length() > 0) { - return " (" + log.toString() + ")"; + return " ("+log.toString()+")"; } else { return ""; } @@ -855,24 +1031,20 @@ public class FHIRPathEngine { public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { this.appInfo = appInfo; this.context = context; - this.focusResource = resource; - this.rootResource = rootResource; + this.focusResource = resource; + this.rootResource = rootResource; this.thisItem = thisItem; this.index = 0; } - public Base getFocusResource() { return focusResource; } - public Base getRootResource() { return rootResource; } - public Base getThisItem() { return thisItem; } - public List getTotal() { return total; } @@ -880,7 +1052,6 @@ public class FHIRPathEngine { public void next() { index++; } - public Base getIndex() { return new IntegerType(index); } @@ -915,8 +1086,8 @@ public class FHIRPathEngine { } } - private class ExecutionTypeContext { - private Object appInfo; + private static class ExecutionTypeContext { + private Object appInfo; private String resource; private TypeDetails context; private TypeDetails thisItem; @@ -931,11 +1102,9 @@ public class FHIRPathEngine { this.thisItem = thisItem; } - public String getResource() { return resource; } - public TypeDetails getThisItem() { return thisItem; } @@ -947,7 +1116,7 @@ public class FHIRPathEngine { public TypeDetails getDefinedVariable(String name) { return definedVariables == null ? null : definedVariables.get(name); } - + public void setDefinedVariable(String name, TypeDetails value) { if (isSystemVariable(name)) throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE); @@ -970,12 +1139,10 @@ public class FHIRPathEngine { ExpressionNode wrapper = null; SourceLocation c = lexer.getCurrentStartLocation(); result.setStart(lexer.getCurrentLocation()); - // special: +/- represents a unary operation at this point, but cannot be a - // feature of the lexer, since that's not always true. + // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. // so we back correct for both +/- and as part of a numeric constant below. - // special: +/- represents a unary operation at this point, but cannot be a - // feature of the lexer, since that's not always true. + // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. // so we back correct for both +/- and as part of a numeric constant below. if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) { wrapper = new ExpressionNode(lexer.nextId()); @@ -990,8 +1157,7 @@ public class FHIRPathEngine { } else if (lexer.isConstant()) { boolean isString = lexer.isStringConstant(); if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) { - // the grammar says that this is a unary operation; it affects the correct - // processing order of the inner operations + // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations wrapper = new ExpressionNode(lexer.nextId()); wrapper.setKind(Kind.Unary); wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1))); @@ -1001,15 +1167,11 @@ public class FHIRPathEngine { } result.setConstant(processConstant(lexer)); result.setKind(Kind.Constant); - if (!isString && !lexer.done() - && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) - && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", - "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) { + if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) { // it's a quantity String ucum = null; String unit = null; - if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", - "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { + if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { String s = lexer.take(); unit = s; if (s.equals("year") || s.equals("years")) { @@ -1028,12 +1190,11 @@ public class FHIRPathEngine { ucum = "s"; } else { // (s.equals("millisecond") || s.equals("milliseconds")) ucum = "ms"; - } + } } else { ucum = lexer.readConstant("units"); } - result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit) - .setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum)); + result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum)); } result.setEnd(lexer.getCurrentLocation()); } else if ("(".equals(lexer.getCurrent())) { @@ -1041,13 +1202,13 @@ public class FHIRPathEngine { result.setKind(Kind.Group); result.setGroup(parseExpression(lexer, true)); if (!")".equals(lexer.getCurrent())) { - throw lexer.error("Found " + lexer.getCurrent() + " expecting a \")\""); + throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); } result.setEnd(lexer.getCurrentLocation()); lexer.next(); } else { if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) { - throw lexer.error("Found " + lexer.getCurrent() + " expecting a token name"); + throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); } if (lexer.isFixedName()) { result.setName(lexer.readFixedName("Path Name")); @@ -1056,7 +1217,7 @@ public class FHIRPathEngine { } result.setEnd(lexer.getCurrentLocation()); if (!result.checkName()) { - throw lexer.error("Found " + result.getName() + " expecting a valid token name"); + throw lexer.error("Found "+result.getName()+" expecting a valid token name"); } if ("(".equals(lexer.getCurrent())) { Function f = Function.fromCode(result.getName()); @@ -1066,20 +1227,19 @@ public class FHIRPathEngine { details = hostServices.resolveFunction(this, result.getName()); } if (details == null) { - throw lexer.error("The name " + result.getName() + " is not a valid function name"); + throw lexer.error("The name "+result.getName()+" is not a valid function name"); } f = Function.Custom; } result.setKind(Kind.Function); result.setFunction(f); lexer.next(); - while (!")".equals(lexer.getCurrent())) { + while (!")".equals(lexer.getCurrent())) { result.getParameters().add(parseExpression(lexer, true)); if (",".equals(lexer.getCurrent())) { lexer.next(); } else if (!")".equals(lexer.getCurrent())) { - throw lexer.error( - "The token " + lexer.getCurrent() + " is not expected here - either a \",\" or a \")\" expected"); + throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); } } result.setEnd(lexer.getCurrentLocation()); @@ -1097,7 +1257,7 @@ public class FHIRPathEngine { item.setFunction(ExpressionNode.Function.Item); item.getParameters().add(parseExpression(lexer, true)); if (!lexer.getCurrent().equals("]")) { - throw lexer.error("The token " + lexer.getCurrent() + " is not expected here - a \"]\" expected"); + throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); } lexer.next(); result.setInner(item); @@ -1128,14 +1288,12 @@ public class FHIRPathEngine { } private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { - node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); - node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); - node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); - node = gatherPrecedence(lexer, node, - EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); + node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); + node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); + node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); + node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); - node = gatherPrecedence(lexer, node, - EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); + node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); // last: implies @@ -1143,10 +1301,10 @@ public class FHIRPathEngine { } private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet ops) { - // work : boolean; - // focus, node, group : ExpressionNode; + // work : boolean; + // focus, node, group : ExpressionNode; - assert (start.isProximal()); + assert(start.isProximal()); // is there anything to do? boolean work = false; @@ -1161,7 +1319,7 @@ public class FHIRPathEngine { work = work || ops.contains(focus.getOperation()); focus = focus.getOpNext(); } - } + } if (!work) { return start; } @@ -1186,9 +1344,8 @@ public class FHIRPathEngine { } // now, at this point: - // group is the group we are adding to, it already has a .group property filled - // out. - // focus points at the group.group + // group is the group we are adding to, it already has a .group property filled out. + // focus points at the group.group do { // run until we find the end of the sequence while (ops.contains(focus.getOperation())) { @@ -1202,21 +1359,23 @@ public class FHIRPathEngine { // now look for another sequence, and start it ExpressionNode node = group; focus = group.getOpNext(); - if (focus != null) { + if (focus != null) { while (focus != null && !ops.contains(focus.getOperation())) { node = focus; focus = focus.getOpNext(); } - if (focus != null) { // && (focus.Operation in Ops) - must be true + if (focus != null) { // && (focus.Operation in Ops) - must be true group = newGroup(lexer, focus); node.setOpNext(group); } } } - } while (focus != null && focus.getOperation() != null); + } + while (focus != null && focus.getOperation() != null); return start; } + private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { ExpressionNode result = new ExpressionNode(lexer.nextId()); result.setKind(Kind.Group); @@ -1240,240 +1399,136 @@ public class FHIRPathEngine { } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { return new FHIRConstant(lexer.take()); } else { - throw lexer.error("Invalid Constant " + lexer.getCurrent()); + throw lexer.error("Invalid Constant "+lexer.getCurrent()); } } - // procedure CheckParamCount(c : integer); - // begin - // if exp.Parameters.Count <> c then - // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' - // parameters', offset); - // end; + // procedure CheckParamCount(c : integer); + // begin + // if exp.Parameters.Count <> c then + // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); + // end; - private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) - throws FHIRLexerException { + private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { if (exp.getParameters().size() != count) { - throw lexer.error("The function \"" + exp.getName() + "\" requires " + Integer.toString(count) + " parameters", - location.toString()); + throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString(), location); } return true; } - private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, - int countMax) throws FHIRLexerException { + private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) { - throw lexer.error("The function \"" + exp.getName() + "\" requires between " + Integer.toString(countMin) - + " and " + Integer.toString(countMax) + " parameters", location.toString()); + throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString(), location); } return true; } - private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) - throws FHIRLexerException { + private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { switch (exp.getFunction()) { - case Empty: - return checkParamCount(lexer, location, exp, 0); - case Not: - return checkParamCount(lexer, location, exp, 0); - case Exists: - return checkParamCount(lexer, location, exp, 0, 1); - case SubsetOf: - return checkParamCount(lexer, location, exp, 1); - case SupersetOf: - return checkParamCount(lexer, location, exp, 1); - case IsDistinct: - return checkParamCount(lexer, location, exp, 0); - case Distinct: - return checkParamCount(lexer, location, exp, 0); - case Count: - return checkParamCount(lexer, location, exp, 0); - case Where: - return checkParamCount(lexer, location, exp, 1); - case Select: - return checkParamCount(lexer, location, exp, 1); - case All: - return checkParamCount(lexer, location, exp, 0, 1); - case Repeat: - return checkParamCount(lexer, location, exp, 1); - case Aggregate: - return checkParamCount(lexer, location, exp, 1, 2); - case Item: - return checkParamCount(lexer, location, exp, 1); - case As: - return checkParamCount(lexer, location, exp, 1); - case OfType: - return checkParamCount(lexer, location, exp, 1); - case Type: - return checkParamCount(lexer, location, exp, 0); - case Is: - return checkParamCount(lexer, location, exp, 1); - case Single: - return checkParamCount(lexer, location, exp, 0); - case First: - return checkParamCount(lexer, location, exp, 0); - case Last: - return checkParamCount(lexer, location, exp, 0); - case Tail: - return checkParamCount(lexer, location, exp, 0); - case Skip: - return checkParamCount(lexer, location, exp, 1); - case Take: - return checkParamCount(lexer, location, exp, 1); - case Union: - return checkParamCount(lexer, location, exp, 1); - case Combine: - return checkParamCount(lexer, location, exp, 1); - case Intersect: - return checkParamCount(lexer, location, exp, 1); - case Exclude: - return checkParamCount(lexer, location, exp, 1); - case Iif: - return checkParamCount(lexer, location, exp, 2, 3); - case Lower: - return checkParamCount(lexer, location, exp, 0); - case Upper: - return checkParamCount(lexer, location, exp, 0); - case ToChars: - return checkParamCount(lexer, location, exp, 0); - case IndexOf: - return checkParamCount(lexer, location, exp, 1); - case Substring: - return checkParamCount(lexer, location, exp, 1, 2); - case StartsWith: - return checkParamCount(lexer, location, exp, 1); - case EndsWith: - return checkParamCount(lexer, location, exp, 1); - case Matches: - return checkParamCount(lexer, location, exp, 1); - case MatchesFull: - return checkParamCount(lexer, location, exp, 1); - case ReplaceMatches: - return checkParamCount(lexer, location, exp, 2); - case Contains: - return checkParamCount(lexer, location, exp, 1); - case Replace: - return checkParamCount(lexer, location, exp, 2); - case Length: - return checkParamCount(lexer, location, exp, 0); - case Children: - return checkParamCount(lexer, location, exp, 0); - case Descendants: - return checkParamCount(lexer, location, exp, 0); - case MemberOf: - return checkParamCount(lexer, location, exp, 1); - case Trace: - return checkParamCount(lexer, location, exp, 1, 2); - case DefineVariable: - return checkParamCount(lexer, location, exp, 1, 2); - case Check: - return checkParamCount(lexer, location, exp, 2); - case Today: - return checkParamCount(lexer, location, exp, 0); - case Now: - return checkParamCount(lexer, location, exp, 0); - case Resolve: - return checkParamCount(lexer, location, exp, 0); - case Extension: - return checkParamCount(lexer, location, exp, 1); - case AllFalse: - return checkParamCount(lexer, location, exp, 0); - case AnyFalse: - return checkParamCount(lexer, location, exp, 0); - case AllTrue: - return checkParamCount(lexer, location, exp, 0); - case AnyTrue: - return checkParamCount(lexer, location, exp, 0); - case HasValue: - return checkParamCount(lexer, location, exp, 0); - case Encode: - return checkParamCount(lexer, location, exp, 1); - case Decode: - return checkParamCount(lexer, location, exp, 1); - case Escape: - return checkParamCount(lexer, location, exp, 1); - case Unescape: - return checkParamCount(lexer, location, exp, 1); - case Trim: - return checkParamCount(lexer, location, exp, 0); - case Split: - return checkParamCount(lexer, location, exp, 1); - case Join: - return checkParamCount(lexer, location, exp, 1); - case HtmlChecks1: - return checkParamCount(lexer, location, exp, 0); - case HtmlChecks2: - return checkParamCount(lexer, location, exp, 0); - case Comparable: - return checkParamCount(lexer, location, exp, 1); - case ToInteger: - return checkParamCount(lexer, location, exp, 0); - case ToDecimal: - return checkParamCount(lexer, location, exp, 0); - case ToString: - return checkParamCount(lexer, location, exp, 0); - case ToQuantity: - return checkParamCount(lexer, location, exp, 0); - case ToBoolean: - return checkParamCount(lexer, location, exp, 0); - case ToDateTime: - return checkParamCount(lexer, location, exp, 0); - case ToTime: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToInteger: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToDecimal: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToString: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToQuantity: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToBoolean: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToDateTime: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToDate: - return checkParamCount(lexer, location, exp, 0); - case ConvertsToTime: - return checkParamCount(lexer, location, exp, 0); - case ConformsTo: - return checkParamCount(lexer, location, exp, 1); - case Round: - return checkParamCount(lexer, location, exp, 0, 1); - case Sqrt: - return checkParamCount(lexer, location, exp, 0); - case Abs: - return checkParamCount(lexer, location, exp, 0); - case Ceiling: - return checkParamCount(lexer, location, exp, 0); - case Exp: - return checkParamCount(lexer, location, exp, 0); - case Floor: - return checkParamCount(lexer, location, exp, 0); - case Ln: - return checkParamCount(lexer, location, exp, 0); - case Log: - return checkParamCount(lexer, location, exp, 1); - case Power: - return checkParamCount(lexer, location, exp, 1); - case Truncate: - return checkParamCount(lexer, location, exp, 0); - case LowBoundary: - return checkParamCount(lexer, location, exp, 0, 1); - case HighBoundary: - return checkParamCount(lexer, location, exp, 0, 1); - case Precision: - return checkParamCount(lexer, location, exp, 0); - - case Custom: - return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); + case Empty: return checkParamCount(lexer, location, exp, 0); + case Not: return checkParamCount(lexer, location, exp, 0); + case Exists: return checkParamCount(lexer, location, exp, 0, 1); + case SubsetOf: return checkParamCount(lexer, location, exp, 1); + case SupersetOf: return checkParamCount(lexer, location, exp, 1); + case IsDistinct: return checkParamCount(lexer, location, exp, 0); + case Distinct: return checkParamCount(lexer, location, exp, 0); + case Count: return checkParamCount(lexer, location, exp, 0); + case Where: return checkParamCount(lexer, location, exp, 1); + case Select: return checkParamCount(lexer, location, exp, 1); + case All: return checkParamCount(lexer, location, exp, 0, 1); + case Repeat: return checkParamCount(lexer, location, exp, 1); + case Aggregate: return checkParamCount(lexer, location, exp, 1, 2); + case Item: return checkParamCount(lexer, location, exp, 1); + case As: return checkParamCount(lexer, location, exp, 1); + case OfType: return checkParamCount(lexer, location, exp, 1); + case Type: return checkParamCount(lexer, location, exp, 0); + case Is: return checkParamCount(lexer, location, exp, 1); + case Single: return checkParamCount(lexer, location, exp, 0); + case First: return checkParamCount(lexer, location, exp, 0); + case Last: return checkParamCount(lexer, location, exp, 0); + case Tail: return checkParamCount(lexer, location, exp, 0); + case Skip: return checkParamCount(lexer, location, exp, 1); + case Take: return checkParamCount(lexer, location, exp, 1); + case Union: return checkParamCount(lexer, location, exp, 1); + case Combine: return checkParamCount(lexer, location, exp, 1); + case Intersect: return checkParamCount(lexer, location, exp, 1); + case Exclude: return checkParamCount(lexer, location, exp, 1); + case Iif: return checkParamCount(lexer, location, exp, 2,3); + case Lower: return checkParamCount(lexer, location, exp, 0); + case Upper: return checkParamCount(lexer, location, exp, 0); + case ToChars: return checkParamCount(lexer, location, exp, 0); + case IndexOf : return checkParamCount(lexer, location, exp, 1); + case Substring: return checkParamCount(lexer, location, exp, 1, 2); + case StartsWith: return checkParamCount(lexer, location, exp, 1); + case EndsWith: return checkParamCount(lexer, location, exp, 1); + case Matches: return checkParamCount(lexer, location, exp, 1); + case MatchesFull: return checkParamCount(lexer, location, exp, 1); + case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); + case Contains: return checkParamCount(lexer, location, exp, 1); + case Replace: return checkParamCount(lexer, location, exp, 2); + case Length: return checkParamCount(lexer, location, exp, 0); + case Children: return checkParamCount(lexer, location, exp, 0); + case Descendants: return checkParamCount(lexer, location, exp, 0); + case MemberOf: return checkParamCount(lexer, location, exp, 1); + case Trace: return checkParamCount(lexer, location, exp, 1, 2); + case DefineVariable: return checkParamCount(lexer, location, exp, 1, 2); + case Check: return checkParamCount(lexer, location, exp, 2); + case Today: return checkParamCount(lexer, location, exp, 0); + case Now: return checkParamCount(lexer, location, exp, 0); + case Resolve: return checkParamCount(lexer, location, exp, 0); + case Extension: return checkParamCount(lexer, location, exp, 1); + case AllFalse: return checkParamCount(lexer, location, exp, 0); + case AnyFalse: return checkParamCount(lexer, location, exp, 0); + case AllTrue: return checkParamCount(lexer, location, exp, 0); + case AnyTrue: return checkParamCount(lexer, location, exp, 0); + case HasValue: return checkParamCount(lexer, location, exp, 0); + case Encode: return checkParamCount(lexer, location, exp, 1); + case Decode: return checkParamCount(lexer, location, exp, 1); + case Escape: return checkParamCount(lexer, location, exp, 1); + case Unescape: return checkParamCount(lexer, location, exp, 1); + case Trim: return checkParamCount(lexer, location, exp, 0); + case Split: return checkParamCount(lexer, location, exp, 1); + case Join: return checkParamCount(lexer, location, exp, 0, 1); + case HtmlChecks1: return checkParamCount(lexer, location, exp, 0); + case HtmlChecks2: return checkParamCount(lexer, location, exp, 0); + case Comparable: return checkParamCount(lexer, location, exp, 1); + case ToInteger: return checkParamCount(lexer, location, exp, 0); + case ToDecimal: return checkParamCount(lexer, location, exp, 0); + case ToString: return checkParamCount(lexer, location, exp, 0); + case ToQuantity: return checkParamCount(lexer, location, exp, 0); + case ToBoolean: return checkParamCount(lexer, location, exp, 0); + case ToDateTime: return checkParamCount(lexer, location, exp, 0); + case ToTime: return checkParamCount(lexer, location, exp, 0); + case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0); + case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0); + case ConvertsToString: return checkParamCount(lexer, location, exp, 0); + case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0); + case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0); + case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0); + case ConvertsToDate: return checkParamCount(lexer, location, exp, 0); + case ConvertsToTime: return checkParamCount(lexer, location, exp, 0); + case ConformsTo: return checkParamCount(lexer, location, exp, 1); + case Round: return checkParamCount(lexer, location, exp, 0, 1); + case Sqrt: return checkParamCount(lexer, location, exp, 0); + case Abs: return checkParamCount(lexer, location, exp, 0); + case Ceiling: return checkParamCount(lexer, location, exp, 0); + case Exp: return checkParamCount(lexer, location, exp, 0); + case Floor: return checkParamCount(lexer, location, exp, 0); + case Ln: return checkParamCount(lexer, location, exp, 0); + case Log: return checkParamCount(lexer, location, exp, 1); + case Power: return checkParamCount(lexer, location, exp, 1); + case Truncate: return checkParamCount(lexer, location, exp, 0); + case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1); + case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1); + case Precision: return checkParamCount(lexer, location, exp, 0); + case hasTemplateIdOf: return checkParamCount(lexer, location, exp, 1); + case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); } return false; } - private List execute(ExecutionContext inContext, List focus, ExpressionNode exp, boolean atEntry) - throws FHIRException { - // System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); + private List execute(ExecutionContext inContext, List focus, ExpressionNode exp, boolean atEntry) throws FHIRException { + // System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); ExecutionContext context = contextForParameter(inContext); List work = new ArrayList(); switch (exp.getKind()) { @@ -1495,7 +1550,7 @@ public class FHIRPathEngine { work.add(base); } } - } + } } break; case Function: @@ -1522,33 +1577,34 @@ public class FHIRPathEngine { List work2 = preOperate(work, last.getOperation(), exp); if (work2 != null) { work = work2; - } else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { + } + else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { work2 = executeTypeName(context, focus, next, false); work = operate(context, work, last.getOperation(), work2, last); } else { work2 = execute(context, focus, next, true); work = operate(context, work, last.getOperation(), work2, last); - // System.out.println("Result of {'"+last.toString()+" - // "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); + // System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); } last = next; next = next.getOpNext(); } } - // System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); + // System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); return work; } private List executeTypeName(ExecutionContext context, List focus, ExpressionNode next, boolean atEntry) { List result = new ArrayList(); if (next.getInner() != null) { - result.add(new StringType(next.getName() + "." + next.getInner().getName())); - } else { + result.add(new StringType(next.getName()+"."+next.getInner().getName())); + } else { result.add(new StringType(next.getName())); } return result; } + private List preOperate(List left, Operation operation, ExpressionNode expr) throws PathEngineException { if (left.size() == 0) { return null; @@ -1559,9 +1615,9 @@ public class FHIRPathEngine { case Or: return isBoolean(left, true) ? makeBoolean(true) : null; case Implies: - Equality v = asBool(left, expr); + Equality v = asBool(left, expr); return v == Equality.False ? makeBoolean(true) : null; - default: + default: return null; } } @@ -1577,15 +1633,13 @@ public class FHIRPathEngine { return res; } - private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, - boolean atEntry) throws PathEngineException, DefinitionException { + private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); } - private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, boolean atEntry) - throws PathEngineException, DefinitionException { - ExecutionTypeContext context = contextForParameter(inContext); - TypeDetails result = new TypeDetails(null); + private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, Set elementDependencies, boolean atEntry, boolean canBeNone, ExpressionNode container) throws PathEngineException, DefinitionException { + ExecutionTypeContext context = contextForParameter(inContext); + TypeDetails result = new TypeDetails(null); switch (exp.getKind()) { case Name: if (atEntry && exp.getName().equals("$this")) { @@ -1598,15 +1652,20 @@ public class FHIRPathEngine { result.update(executeContextType(context, exp.getName(), exp, false)); } else { for (String s : focus.getTypes()) { - result.update(executeType(s, exp, atEntry)); + result.update(executeType(s, exp, atEntry, focus, elementDependencies)); } if (result.hasNoTypes()) { - throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe()); + if (!canBeNone) { + throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe()); + } else { + // return result; + } } } + doSQLOnFHIRCheck(result, exp); break; case Function: - result.update(evaluateFunctionType(context, focus, exp)); + result.update(evaluateFunctionType(context, focus, exp, elementDependencies, container)); break; case Unary: result.addType(TypeDetails.FP_Integer); @@ -1617,12 +1676,12 @@ public class FHIRPathEngine { result.update(resolveConstantType(context, exp.getConstant(), exp, true)); break; case Group: - result.update(executeType(context, focus, exp.getGroup(), atEntry)); + result.update(executeType(context, focus, exp.getGroup(), elementDependencies, atEntry, canBeNone, exp)); } exp.setTypes(result); if (exp.getInner() != null) { - result = executeType(context, result, exp.getInner(), false); + result = executeType(context, result, exp.getInner(), elementDependencies, false, false, exp); } if (exp.isProximal() && exp.getOperation() != null) { @@ -1634,7 +1693,7 @@ public class FHIRPathEngine { if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { work = executeTypeName(context, focus, next, atEntry); } else { - work = executeType(context, focus, next, atEntry); + work = executeType(context, focus, next, elementDependencies, atEntry, canBeNone, exp); } result = operateTypes(result, last.getOperation(), work, last); last = next; @@ -1645,6 +1704,18 @@ public class FHIRPathEngine { return result; } + private void doSQLOnFHIRCheck(TypeDetails focus, ExpressionNode expr) { + if (emitSQLonFHIRWarning) { + // special Logic for SQL-on-FHIR: + if (focus.isChoice()) { + if (expr.getInner() == null || expr.getInner().getFunction() != Function.OfType) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER)); + } + } else if (expr.getInner() != null && expr.getInner().getFunction() == Function.OfType) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER)); + } + } + } private List resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { if (constant == null) { return new ArrayList(); @@ -1699,11 +1770,11 @@ public class FHIRPathEngine { } if (time.length() == 2) { - time = time + ":00:00"; + time = time+":00:00"; temp = TemporalPrecisionEnum.MINUTE; } else if (time.length() == 5) { temp = TemporalPrecisionEnum.MINUTE; - time = time + ":00"; + time = time+":00"; } else if (time.contains(".")) { temp = TemporalPrecisionEnum.MILLI; } else { @@ -1720,10 +1791,10 @@ public class FHIRPathEngine { return tt.noExtensions(); } } else if (time != null) { - DateTimeType dt = new DateTimeType(date + "T" + time + (tz == null ? "" : tz)); + DateTimeType dt = new DateTimeType(date+"T"+time+(tz == null ? "" : tz)); dt.setPrecision(temp); return dt.noExtensions(); - } else { + } else { return new DateType(date).noExtensions(); } } @@ -1744,8 +1815,7 @@ public class FHIRPathEngine { return false; } - private List resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) - throws PathEngineException { + private List resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { if (s.equals("%sct")) { return new ArrayList(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions())); } else if (s.equals("%loinc")) { @@ -1759,7 +1829,7 @@ public class FHIRPathEngine { return new ArrayList(Arrays.asList(context.focusResource)); } else if (s.equals("%rootResource")) { if (context.rootResource == null) { - throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource"); + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource"); } return new ArrayList(Arrays.asList(context.rootResource)); } else if (s.equals("%context")) { @@ -1767,14 +1837,11 @@ public class FHIRPathEngine { } else if (s.equals("%us-zip")) { return new ArrayList(Arrays.asList(new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions())); } else if (s.startsWith("%`vs-")) { - return new ArrayList(Arrays.asList( - new StringType("http://hl7.org/fhir/ValueSet/" + s.substring(5, s.length() - 1) + "").noExtensions())); + return new ArrayList(Arrays.asList(new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions())); } else if (s.startsWith("%`cs-")) { - return new ArrayList( - Arrays.asList(new StringType("http://hl7.org/fhir/" + s.substring(5, s.length() - 1) + "").noExtensions())); + return new ArrayList(Arrays.asList(new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions())); } else if (s.startsWith("%`ext-")) { - return new ArrayList(Arrays.asList( - new StringType("http://hl7.org/fhir/StructureDefinition/" + s.substring(6, s.length() - 1)).noExtensions())); + return new ArrayList(Arrays.asList(new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions())); } else if (hostServices == null) { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); } else { @@ -1782,24 +1849,25 @@ public class FHIRPathEngine { } } + private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException { StringBuilder b = new StringBuilder(); int i = 1; - while (i < s.length() - 1) { + while (i < s.length()-1) { char ch = s.charAt(i); if (ch == '\\') { i++; switch (s.charAt(i)) { - case 't': + case 't': b.append('\t'); break; case 'r': b.append('\r'); break; - case 'n': + case 'n': b.append('\n'); break; - case 'f': + case 'f': b.append('\f'); break; case '\'': @@ -1811,20 +1879,20 @@ public class FHIRPathEngine { case '`': b.append('`'); break; - case '\\': + case '\\': b.append('\\'); break; - case '/': + case '/': b.append('/'); break; case 'u': i++; - int uc = Integer.parseInt(s.substring(i, i + 4), 16); + int uc = Integer.parseInt(s.substring(i, i+4), 16); b.append(Character.toString(uc)); i = i + 3; break; default: - throw lexer.error("Unknown character escape \\" + s.charAt(i)); + throw lexer.error("Unknown FHIRPath character escape \\"+s.charAt(i)); } i++; } else { @@ -1835,61 +1903,36 @@ public class FHIRPathEngine { return b.toString(); } - private List operate(ExecutionContext context, List left, Operation operation, List right, - ExpressionNode holder) throws FHIRException { + + private List operate(ExecutionContext context, List left, Operation operation, List right, ExpressionNode holder) throws FHIRException { switch (operation) { - case Equals: - return opEquals(left, right, holder); - case Equivalent: - return opEquivalent(left, right, holder); - case NotEquals: - return opNotEquals(left, right, holder); - case NotEquivalent: - return opNotEquivalent(left, right, holder); - case LessThan: - return opLessThan(left, right, holder); - case Greater: - return opGreater(left, right, holder); - case LessOrEqual: - return opLessOrEqual(left, right, holder); - case GreaterOrEqual: - return opGreaterOrEqual(left, right, holder); - case Union: - return opUnion(left, right, holder); - case In: - return opIn(left, right, holder); - case MemberOf: - return opMemberOf(context, left, right, holder); - case Contains: - return opContains(left, right, holder); - case Or: - return opOr(left, right, holder); - case And: - return opAnd(left, right, holder); - case Xor: - return opXor(left, right, holder); - case Implies: - return opImplies(left, right, holder); - case Plus: - return opPlus(left, right, holder); - case Times: - return opTimes(left, right, holder); - case Minus: - return opMinus(left, right, holder); - case Concatenate: - return opConcatenate(left, right, holder); - case DivideBy: - return opDivideBy(left, right, holder); - case Div: - return opDiv(left, right, holder); - case Mod: - return opMod(left, right, holder); - case Is: - return opIs(left, right, holder); - case As: - return opAs(left, right, holder); - default: - throw new Error("Not Done Yet: " + operation.toCode()); + case Equals: return opEquals(left, right, holder); + case Equivalent: return opEquivalent(left, right, holder); + case NotEquals: return opNotEquals(left, right, holder); + case NotEquivalent: return opNotEquivalent(left, right, holder); + case LessThan: return opLessThan(left, right, holder); + case Greater: return opGreater(left, right, holder); + case LessOrEqual: return opLessOrEqual(left, right, holder); + case GreaterOrEqual: return opGreaterOrEqual(left, right, holder); + case Union: return opUnion(left, right, holder); + case In: return opIn(left, right, holder); + case MemberOf: return opMemberOf(context, left, right, holder); + case Contains: return opContains(left, right, holder); + case Or: return opOr(left, right, holder); + case And: return opAnd(left, right, holder); + case Xor: return opXor(left, right, holder); + case Implies: return opImplies(left, right, holder); + case Plus: return opPlus(left, right, holder); + case Times: return opTimes(left, right, holder); + case Minus: return opMinus(left, right, holder); + case Concatenate: return opConcatenate(left, right, holder); + case DivideBy: return opDivideBy(left, right, holder); + case Div: return opDiv(left, right, holder); + case Mod: return opMod(left, right, holder); + case Is: return opIs(left, right, holder); + case As: return opAs(left, right, holder); + default: + throw new Error("Not Done Yet: "+operation.toCode()); } } @@ -1900,11 +1943,10 @@ public class FHIRPathEngine { } else { String tn = convertToString(right); if (!isKnownType(tn)) { - throw new PathEngineException("The type " + tn + " is not valid"); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); } if (!doNotEnforceAsSingletonRule && left.size() > 1) { - throw new PathEngineException( - "Attempt to use as on more than one item (" + left.size() + ", '" + expr.toString() + "')"); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, left.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); } for (Base nextLeft : left) { if (compareTypeNames(tn, nextLeft.fhirType())) { @@ -1917,16 +1959,15 @@ public class FHIRPathEngine { private boolean compareTypeNames(String left, String right) { if (doNotEnforceAsCaseSensitive) { - return left.equalsIgnoreCase(right); + return left.equalsIgnoreCase(right); } else { - return left.equals(right); + return left.equals(right); } } private boolean isKnownType(String tn) { if (!tn.contains(".")) { - if (Utilities.existsInList(tn, "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", - "SimpleTypeInfo", "ClassInfo")) { + if (Utilities.existsInList(tn, "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { return true; } try { @@ -1940,14 +1981,19 @@ public class FHIRPathEngine { return false; } if ("System".equals(t[0])) { - return Utilities.existsInList(t[1], "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", - "SimpleTypeInfo", "ClassInfo"); - } else if ("FHIR".equals(t[0])) { + return Utilities.existsInList(t[1], "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo"); + } else if ("FHIR".equals(t[0])) { try { return worker.fetchTypeDefinition(t[1]) != null; } catch (Exception e) { return false; } + } else if ("CDA".equals(t[0])) { + try { + return worker.fetchTypeDefinition(Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", t[1])) != null; + } catch (Exception e) { + return false; + } } else { return false; } @@ -1956,15 +2002,14 @@ public class FHIRPathEngine { private List opIs(List left, List right, ExpressionNode expr) { List result = new ArrayList(); if (left.size() == 0 || right.size() == 0) { - } else if (left.size() != 1 || right.size() != 1) + } else if (left.size() != 1 || right.size() != 1) result.add(new BooleanType(false).noExtensions()); else { String tn = convertToString(right); if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element) { result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); } else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions()) { - result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) - || ("System." + Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions()); + result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions()); } else { if (left.get(0).fhirType().equals(tn)) { result.add(new BooleanType(true).noExtensions()); @@ -1974,48 +2019,76 @@ public class FHIRPathEngine { if (tn.equals(sd.getType())) { return makeBoolean(true); } - sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); } return makeBoolean(false); - } + } } } return result; } + + private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { + if (left.isList() && !right.isList()) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT)); + } else if (!left.isList() && right.isList()) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT)); + } + } + + private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { + if (left.isList()) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT)); + } + if (right.isList()) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT)); + } + } + private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { switch (operation) { - case Equals: + case Equals: + checkCardinalityForComparabilitySame(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Equivalent: + case Equivalent: + checkCardinalityForComparabilitySame(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case NotEquals: + case NotEquals: + checkCardinalityForComparabilitySame(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case NotEquivalent: + case NotEquivalent: + checkCardinalityForComparabilitySame(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case LessThan: + case LessThan: + checkCardinalityForSingle(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Greater: + case Greater: + checkCardinalityForSingle(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case LessOrEqual: + case LessOrEqual: + checkCardinalityForSingle(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case GreaterOrEqual: + case GreaterOrEqual: + checkCardinalityForSingle(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Is: + case Is: + checkCardinalityForSingle(left, operation, right, expr); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case As: - return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); - case Union: - return left.union(right); - case Or: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case And: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Xor: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Implies: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Times: + case As: + checkCardinalityForSingle(left, operation, right, expr); + TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); + if (td.typesHaveTargets()) { + td.addTargets(left.getTargets()); + } + return td; + case Union: return left.union(right); + case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Times: + checkCardinalityForSingle(left, operation, right, expr); TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Integer); @@ -2023,7 +2096,8 @@ public class FHIRPathEngine { result.addType(TypeDetails.FP_Decimal); } return result; - case DivideBy: + case DivideBy: + checkCardinalityForSingle(left, operation, right, expr); result = new TypeDetails(CollectionStatus.SINGLETON); if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Decimal); @@ -2032,28 +2106,28 @@ public class FHIRPathEngine { } return result; case Concatenate: + checkCardinalityForSingle(left, operation, right, expr); result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return result; case Plus: + checkCardinalityForSingle(left, operation, right, expr); result = new TypeDetails(CollectionStatus.SINGLETON); if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Integer); } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { result.addType(TypeDetails.FP_Decimal); - } else if (left.hasType(worker, "string", "id", "code", "uri") - && right.hasType(worker, "string", "id", "code", "uri")) { + } else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) { result.addType(TypeDetails.FP_String); } else if (left.hasType(worker, "date", "dateTime", "instant")) { if (right.hasType(worker, "Quantity")) { result.addType(left.getType()); } else { - throw new PathEngineException( - String.format("Error in date arithmetic: Unable to add type {0} to {1}", right.getType(), left.getType()), - expr.getOpStart(), expr.toString()); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_PLUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_PLUS, expr.getOpStart(), expr.toString()); } } return result; case Minus: + checkCardinalityForSingle(left, operation, right, expr); result = new TypeDetails(CollectionStatus.SINGLETON); if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Integer); @@ -2065,13 +2139,13 @@ public class FHIRPathEngine { if (right.hasType(worker, "Quantity")) { result.addType(left.getType()); } else { - throw new PathEngineException(String.format("Error in date arithmetic: Unable to subtract type {0} from {1}", - right.getType(), left.getType())); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_MINUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_MINUS, expr.getOpStart(), expr.toString()); } } return result; - case Div: - case Mod: + case Div: + case Mod: + checkCardinalityForSingle(left, operation, right, expr); result = new TypeDetails(CollectionStatus.SINGLETON); if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Integer); @@ -2079,19 +2153,17 @@ public class FHIRPathEngine { result.addType(TypeDetails.FP_Decimal); } return result; - case In: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case MemberOf: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Contains: - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - default: + case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + default: return null; } } + private List opEquals(List left, List right, ExpressionNode expr) { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } @@ -2105,7 +2177,7 @@ public class FHIRPathEngine { Boolean eq = doEquals(left.get(i), right.get(i)); if (eq == null) { nil = true; - } else if (eq == false) { + } else if (eq == false) { res = false; break; } @@ -2134,7 +2206,7 @@ public class FHIRPathEngine { Boolean eq = doEquals(left.get(i), right.get(i)); if (eq == null) { nil = true; - } else if (eq == true) { + } else if (eq == true) { res = false; break; } @@ -2151,7 +2223,7 @@ public class FHIRPathEngine { private String removeTrailingZeros(String s) { if (Utilities.noString(s)) return ""; - int i = s.length() - 1; + int i = s.length()-1; boolean done = false; boolean dot = false; while (i > 0 && !done) { @@ -2164,7 +2236,7 @@ public class FHIRPathEngine { done = true; } } - return s.substring(0, i + 1); + return s.substring(0, i+1); } private boolean decEqual(String left, String right) { @@ -2180,9 +2252,9 @@ public class FHIRPathEngine { private Boolean doEquals(Base left, Base right) { if (left instanceof Quantity && right instanceof Quantity) { return qtyEqual((Quantity) left, (Quantity) right); - } else if (left.isDateTime() && right.isDateTime()) { + } else if (left.isDateTime() && right.isDateTime()) { return datesEqual(left.dateTimeValue(), right.dateTimeValue()); - } else if (left instanceof DecimalType || right instanceof DecimalType) { + } else if (left instanceof DecimalType || right instanceof DecimalType) { return decEqual(left.primitiveValue(), right.primitiveValue()); } else if (left.isPrimitive() && right.isPrimitive()) { return Base.equals(left.primitiveValue(), right.primitiveValue()); @@ -2201,8 +2273,7 @@ public class FHIRPathEngine { if (left.hasType("boolean") && right.hasType("boolean")) { return doEquals(left, right); } - if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") - && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); } if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) { @@ -2231,7 +2302,7 @@ public class FHIRPathEngine { } } else if (v.hasLeft() || v.hasRight()) { return false; - } + } } } else if (t.getLeft().hasValues() || t.getRight().hasValues()) { return false; @@ -2243,7 +2314,7 @@ public class FHIRPathEngine { return true; } else { return false; - } + } } private Boolean qtyEqual(Quantity left, Quantity right) { @@ -2258,7 +2329,7 @@ public class FHIRPathEngine { Pair dr = qtyToCanonicalPair(right); if (dl != null && dr != null) { if (dl.getCode().equals(dr.getCode())) { - return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); + return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); } else { return false; } @@ -2303,10 +2374,10 @@ public class FHIRPathEngine { } private Base pairToQty(Pair p) { - return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org") - .setCode(p.getCode()).noExtensions(); + return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions(); } + private Pair qtyToPair(Quantity q) { if (!"http://unitsofmeasure.org".equals(q.getSystem())) { return null; @@ -2318,6 +2389,7 @@ public class FHIRPathEngine { } } + private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { if (!left.hasValue() && !right.hasValue()) { return true; @@ -2330,7 +2402,7 @@ public class FHIRPathEngine { Pair dr = qtyToCanonicalPair(right); if (dl != null && dr != null) { if (dl.getCode().equals(dr.getCode())) { - return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); + return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); } else { return false; } @@ -2348,6 +2420,8 @@ public class FHIRPathEngine { return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue())); } + + private List opEquivalent(List left, List right, ExpressionNode expr) throws PathEngineException { if (left.size() != right.size()) { return makeBoolean(false); @@ -2370,8 +2444,7 @@ public class FHIRPathEngine { return makeBoolean(res); } - private List opNotEquivalent(List left, List right, ExpressionNode expr) - throws PathEngineException { + private List opNotEquivalent(List left, List right, ExpressionNode expr) throws PathEngineException { if (left.size() != right.size()) { return makeBoolean(true); } @@ -2393,19 +2466,18 @@ public class FHIRPathEngine { return makeBoolean(!res); } - private final static String[] FHIR_TYPES_STRING = new String[] { "string", "uri", "code", "oid", "id", "uuid", "sid", - "markdown", "base64Binary", "canonical", "url" }; + private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url", "xhtml"}; private List opLessThan(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) + if (left.size() == 0 || right.size() == 0) return new ArrayList(); if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { Base l = left.get(0); Base r = right.get(0); - if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { + if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); - } else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) { + } else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) { return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { Integer i = compareDateTimeElements(l, r, false); @@ -2414,7 +2486,7 @@ public class FHIRPathEngine { } else { return makeBoolean(i < 0); } - } else if ((l.hasType("time")) && (r.hasType("time"))) { + } else if ((l.hasType("time")) && (r.hasType("time"))) { Integer i = compareTimeElements(l, r, false); if (i == null) { return makeNull(); @@ -2424,8 +2496,7 @@ public class FHIRPathEngine { } else { throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); } - } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") - && right.get(0).fhirType().equals("Quantity")) { + } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { List lUnit = left.get(0).listChildrenByName("code"); List rUnit = right.get(0).listChildrenByName("code"); if (Base.compareDeep(lUnit, rUnit, true)) { @@ -2446,24 +2517,23 @@ public class FHIRPathEngine { } private List opGreater(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) + if (left.size() == 0 || right.size() == 0) return new ArrayList(); if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { Base l = left.get(0); Base r = right.get(0); if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); - } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) - && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { + } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { Integer i = compareDateTimeElements(l, r, false); if (i == null) { return makeNull(); } else { - return makeBoolean(i > 0); + return makeBoolean(i > 0); } - } else if ((l.hasType("time")) && (r.hasType("time"))) { + } else if ((l.hasType("time")) && (r.hasType("time"))) { Integer i = compareTimeElements(l, r, false); if (i == null) { return makeNull(); @@ -2473,8 +2543,7 @@ public class FHIRPathEngine { } else { throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); } - } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") - && right.get(0).fhirType().equals("Quantity")) { + } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { List lUnit = left.get(0).listChildrenByName("unit"); List rUnit = right.get(0).listChildrenByName("unit"); if (Base.compareDeep(lUnit, rUnit, true)) { @@ -2495,16 +2564,15 @@ public class FHIRPathEngine { } private List opLessOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { Base l = left.get(0); Base r = right.get(0); - if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { + if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); - } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) - && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { + } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { Integer i = compareDateTimeElements(l, r, false); @@ -2523,8 +2591,7 @@ public class FHIRPathEngine { } else { throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); } - } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") - && right.get(0).fhirType().equals("Quantity")) { + } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { List lUnits = left.get(0).listChildrenByName("unit"); String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; List rUnits = right.get(0).listChildrenByName("unit"); @@ -2547,16 +2614,15 @@ public class FHIRPathEngine { } private List opGreaterOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { Base l = left.get(0); Base r = right.get(0); - if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { + if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); - } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) - && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { + } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { Integer i = compareDateTimeElements(l, r, false); @@ -2575,13 +2641,11 @@ public class FHIRPathEngine { } else { throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); } - } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") - && right.get(0).fhirType().equals("Quantity")) { + } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { List lUnit = left.get(0).listChildrenByName("unit"); List rUnit = right.get(0).listChildrenByName("unit"); if (Base.compareDeep(lUnit, rUnit, true)) { - return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), - expr); + return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); } else { if (worker.getUcumService() == null) { return makeBoolean(false); @@ -2597,16 +2661,14 @@ public class FHIRPathEngine { return new ArrayList(); } - private List opMemberOf(ExecutionContext context, List left, List right, ExpressionNode expr) - throws FHIRException { + private List opMemberOf(ExecutionContext context, List left, List right, ExpressionNode expr) throws FHIRException { boolean ans = false; String url = right.get(0).primitiveValue(); - ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) - : worker.fetchResource(ValueSet.class, url); + ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.fetchResource(ValueSet.class, url); if (vs != null) { for (Base l : left) { if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { - if (worker.validateCode(terminologyServiceOptions.withGuessSystem(), l.castToCoding(l), vs).isOk()) { + if (worker.validateCode(terminologyServiceOptions.withGuessSystem() , l.castToCoding(l), vs).isOk()) { ans = true; } } else if (l.fhirType().equals("Coding")) { @@ -2616,13 +2678,12 @@ public class FHIRPathEngine { } else if (l.fhirType().equals("CodeableConcept")) { CodeableConcept cc = l.castToCodeableConcept(l); ValidationResult vr = worker.validateCode(terminologyServiceOptions, cc, vs); - // System.out.println("~~~ "+DataRenderer.display(worker, cc)+ " memberOf - // "+url+": "+vr.toString()); + // System.out.println("~~~ "+DataRenderer.display(worker, cc)+ " memberOf "+url+": "+vr.toString()); if (vr.isOk()) { ans = true; } } else { - // System.out.println("unknown type in opMemberOf: "+l.fhirType()); + // System.out.println("unknown type in opMemberOf: "+l.fhirType()); } } } @@ -2630,10 +2691,10 @@ public class FHIRPathEngine { } private List opIn(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0) { + if (left.size() == 0) { return new ArrayList(); } - if (right.size() == 0) { + if (right.size() == 0) { return makeBoolean(false); } boolean ans = true; @@ -2655,7 +2716,7 @@ public class FHIRPathEngine { } private List opContains(List left, List right, ExpressionNode expr) { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } boolean ans = true; @@ -2677,7 +2738,7 @@ public class FHIRPathEngine { } private List opPlus(List left, List right, ExpressionNode expr) throws PathEngineException { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } if (left.size() > 1) { @@ -2689,26 +2750,27 @@ public class FHIRPathEngine { if (right.size() > 1) { throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "+"); } - if (!right.get(0).isPrimitive() - && !((left.get(0).isDateTime() || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) - && right.get(0).hasType("Quantity"))) { + if (!right.get(0).isPrimitive() && !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) { throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "+", right.get(0).fhirType()); } List result = new ArrayList(); Base l = left.get(0); Base r = right.get(0); - if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { + if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { result.add(new StringType(l.primitiveValue() + r.primitiveValue())); - } else if (l.hasType("integer") && r.hasType("integer")) { + } else if (l.hasType("integer") && r.hasType("integer")) { result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); - } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); - } else if (l.isDateTime() && r.hasType("Quantity")) { - result.add(dateAdd((BaseDateTimeType) l, (Quantity) r, false, expr)); + } else if (l.hasType("date") && r.hasType("Quantity")) { + DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); + result.add(dateAdd(dl, (Quantity) r, false, expr)); + } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { + DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); + result.add(dateAdd(dl, (Quantity) r, false, expr)); } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } @@ -2718,53 +2780,50 @@ public class FHIRPathEngine { int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue(); switch (q.hasCode() ? q.getCode() : q.getUnit()) { - case "years": - case "year": + case "years": + case "year": result.add(Calendar.YEAR, value); break; case "a": - throw new PathEngineException(String - .format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode())); - case "months": - case "month": + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); + case "months": + case "month": result.add(Calendar.MONTH, value); break; case "mo": - throw new PathEngineException(String - .format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode()), - holder.getOpStart(), holder.toString()); - case "weeks": - case "week": + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); + case "weeks": + case "week": case "wk": result.add(Calendar.DAY_OF_MONTH, value * 7); break; - case "days": - case "day": + case "days": + case "day": case "d": result.add(Calendar.DAY_OF_MONTH, value); break; - case "hours": - case "hour": + case "hours": + case "hour": case "h": result.add(Calendar.HOUR, value); break; - case "minutes": - case "minute": + case "minutes": + case "minute": case "min": result.add(Calendar.MINUTE, value); break; - case "seconds": - case "second": + case "seconds": + case "second": case "s": result.add(Calendar.SECOND, value); break; - case "milliseconds": - case "millisecond": - case "ms": + case "milliseconds": + case "millisecond": + case "ms": result.add(Calendar.MILLISECOND, value); break; default: - throw new PathEngineException(String.format("Error in date arithmetic: unrecognized time unit %s", q.getCode())); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString()); } return result; } @@ -2790,9 +2849,9 @@ public class FHIRPathEngine { Base l = left.get(0); Base r = right.get(0); - if (l.hasType("integer") && r.hasType("integer")) { + if (l.hasType("integer") && r.hasType("integer")) { result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); - } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { Pair pl = qtyToPair((Quantity) l); @@ -2802,15 +2861,15 @@ public class FHIRPathEngine { p = worker.getUcumService().multiply(pl, pr); result.add(pairToQty(p)); } catch (UcumException e) { - throw new PathEngineException(e.getMessage(), expr.getOpStart(), expr.toString(), e); + throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #TODO: i18n } } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } + private List opConcatenate(List left, List right, ExpressionNode expr) throws PathEngineException { if (left.size() > 1) { throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&"); @@ -2857,12 +2916,12 @@ public class FHIRPathEngine { return false; } + private List opAnd(List left, List right, ExpressionNode expr) throws PathEngineException { Equality l = asBool(left, expr); Equality r = asBool(right, expr); switch (l) { - case False: - return makeBoolean(false); + case False: return makeBoolean(false); case Null: if (r == Equality.False) { return makeBoolean(false); @@ -2871,12 +2930,9 @@ public class FHIRPathEngine { } case True: switch (r) { - case False: - return makeBoolean(false); - case Null: - return makeNull(); - case True: - return makeBoolean(true); + case False: return makeBoolean(false); + case Null: return makeNull(); + case True: return makeBoolean(true); } } return makeNull(); @@ -2890,8 +2946,7 @@ public class FHIRPathEngine { Equality l = asBool(left, expr); Equality r = asBool(right, expr); switch (l) { - case True: - return makeBoolean(true); + case True: return makeBoolean(true); case Null: if (r == Equality.True) { return makeBoolean(true); @@ -2900,12 +2955,9 @@ public class FHIRPathEngine { } case False: switch (r) { - case False: - return makeBoolean(false); - case Null: - return makeNull(); - case True: - return makeBoolean(true); + case False: return makeBoolean(false); + case Null: return makeNull(); + case True: return makeBoolean(true); } } return makeNull(); @@ -2915,25 +2967,19 @@ public class FHIRPathEngine { Equality l = asBool(left, expr); Equality r = asBool(right, expr); switch (l) { - case True: + case True: switch (r) { - case False: - return makeBoolean(true); - case True: - return makeBoolean(false); - case Null: - return makeNull(); + case False: return makeBoolean(true); + case True: return makeBoolean(false); + case Null: return makeNull(); } case Null: return makeNull(); case False: switch (r) { - case False: - return makeBoolean(false); - case True: - return makeBoolean(true); - case Null: - return makeNull(); + case False: return makeBoolean(false); + case True: return makeBoolean(true); + case Null: return makeNull(); } } return makeNull(); @@ -2941,24 +2987,21 @@ public class FHIRPathEngine { private List opImplies(List left, List right, ExpressionNode expr) throws PathEngineException { Equality eq = asBool(left, expr); - if (eq == Equality.False) { + if (eq == Equality.False) { return makeBoolean(true); } else if (right.size() == 0) { return makeNull(); - } else - switch (asBool(right, expr)) { - case False: - return eq == Equality.Null ? makeNull() : makeBoolean(false); - case Null: - return makeNull(); - case True: - return makeBoolean(true); - } + } else switch (asBool(right, expr)) { + case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); + case Null: return makeNull(); + case True: return makeBoolean(true); + } return makeNull(); } + private List opMinus(List left, List right, ExpressionNode expr) throws PathEngineException { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } if (left.size() > 1) { @@ -2970,9 +3013,7 @@ public class FHIRPathEngine { if (right.size() > 1) { throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "-"); } - if (!right.get(0).isPrimitive() - && !((left.get(0).isDateTime() || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) - && right.get(0).hasType("Quantity"))) { + if (!right.get(0).isPrimitive() && !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) { throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType()); } @@ -2980,21 +3021,24 @@ public class FHIRPathEngine { Base l = left.get(0); Base r = right.get(0); - if (l.hasType("integer") && r.hasType("integer")) { + if (l.hasType("integer") && r.hasType("integer")) { result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); - } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); - } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { + } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { String s = l.primitiveValue(); if ("0".equals(s)) { Quantity qty = (Quantity) r; result.add(qty.copy().setValue(qty.getValue().abs())); } - } else if (l.isDateTime() && r.hasType("Quantity")) { - result.add(dateAdd((BaseDateTimeType) l, (Quantity) r, true, expr)); + } else if (l.hasType("date") && r.hasType("Quantity")) { + DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); + result.add(dateAdd(dl, (Quantity) r, true, expr)); + } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { + DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); + result.add(dateAdd(dl, (Quantity) r, true, expr)); } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } @@ -3020,8 +3064,7 @@ public class FHIRPathEngine { Base l = left.get(0); Base r = right.get(0); - if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") - && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { Decimal d1; try { d1 = new Decimal(l.primitiveValue()); @@ -3041,14 +3084,13 @@ public class FHIRPathEngine { // just return nothing } } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } private List opDiv(List left, List right, ExpressionNode expr) throws PathEngineException { - if (left.size() == 0 || right.size() == 0) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); } if (left.size() > 1) { @@ -3070,10 +3112,10 @@ public class FHIRPathEngine { if (l.hasType("integer") && r.hasType("integer")) { int divisor = Integer.parseInt(r.primitiveValue()); - if (divisor != 0) { + if (divisor != 0) { result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor)); } - } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { Decimal d1; try { d1 = new Decimal(l.primitiveValue()); @@ -3083,8 +3125,7 @@ public class FHIRPathEngine { // just return nothing } } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } @@ -3092,8 +3133,7 @@ public class FHIRPathEngine { private List opMod(List left, List right, ExpressionNode expr) throws PathEngineException { if (left.size() == 0 || right.size() == 0) { return new ArrayList(); - } - if (left.size() > 1) { + } if (left.size() > 1) { throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod"); } if (!left.get(0).isPrimitive()) { @@ -3110,7 +3150,7 @@ public class FHIRPathEngine { Base l = left.get(0); Base r = right.get(0); - if (l.hasType("integer") && r.hasType("integer")) { + if (l.hasType("integer") && r.hasType("integer")) { int modulus = Integer.parseInt(r.primitiveValue()); if (modulus != 0) { result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus)); @@ -3125,15 +3165,14 @@ public class FHIRPathEngine { throw new PathEngineException(e); } } else { - throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), - right.get(0).fhirType()); + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType()); } return result; } - private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, boolean explicitConstant) - throws PathEngineException { - if (constant instanceof BooleanType) { + + private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { + if (constant instanceof BooleanType) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } else if (constant instanceof IntegerType) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); @@ -3144,14 +3183,13 @@ public class FHIRPathEngine { } else if (constant instanceof FHIRConstant) { return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr, explicitConstant); } else if (constant == null) { - return new TypeDetails(CollectionStatus.SINGLETON); + return new TypeDetails(CollectionStatus.SINGLETON); } else { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } } - private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, boolean explicitConstant) - throws PathEngineException { + private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, boolean explicitConstant) throws PathEngineException { if (s.startsWith("@")) { if (s.startsWith("@T")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); @@ -3171,7 +3209,7 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, context.resource); } else if (s.equals("%rootResource")) { if (context.resource == null) { - throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource"); + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource"); } return new TypeDetails(CollectionStatus.SINGLETON, context.resource); } else if (s.equals("%context")) { @@ -3195,13 +3233,17 @@ public class FHIRPathEngine { String varName = s.substring(1); if (context.hasDefinedVariable(varName)) return context.getDefinedVariable(varName); - return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); + TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); + if (v == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); + } else { + return v; + } } } - private List execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) - throws FHIRException { - List result = new ArrayList(); + private List execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { + List result = new ArrayList(); if (atEntry && context.appInfo != null && hostServices != null) { // we'll see if the name matches a constant known by the context. List temp = hostServices.resolveConstant(this, context.appInfo, exp.getName(), true, false); @@ -3210,132 +3252,201 @@ public class FHIRPathEngine { return result; } } - if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start - // up + if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType()); if (sd == null) { // logical model if (exp.getName().equals(item.fhirType())) { - result.add(item); + result.add(item); } } else { while (sd != null) { - if (sd.getType().equals(exp.getName())) { + if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) { result.add(item); break; } - sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); } } } else { getChildrenByName(item, exp.getName(), result); } if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) { - // well, we didn't get a match on the name - we'll see if the name matches a - // constant known by the context. - // (if the name does match, and the user wants to get the constant value, - // they'll have to try harder... + // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. + // (if the name does match, and the user wants to get the constant value, they'll have to try harder... result.addAll(hostServices.resolveConstant(this, context.appInfo, exp.getName(), false, false)); } return result; - } + } private String getParent(String rn) { return null; } - private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, boolean explicitConstant) - throws PathEngineException, DefinitionException { + + private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, boolean explicitConstant) throws PathEngineException, DefinitionException { if (hostServices == null) { throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference"); } return hostServices.resolveConstantType(this, context.appInfo, name, explicitConstant); } - private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) - throws PathEngineException, DefinitionException { - if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special - // case for - // start up + private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set elementDependencies) throws PathEngineException, DefinitionException { + if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up return new TypeDetails(CollectionStatus.SINGLETON, type); } - TypeDetails result = new TypeDetails(null); - getChildTypesByName(type, exp.getName(), result, exp); + TypeDetails result = new TypeDetails(focus.getCollectionStatus()); + getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies); return result; } + + private boolean isAncestor(String wanted, String stated) { + try { + StructureDefinition sd = worker.fetchTypeDefinition(wanted); + while (sd != null) { + if (stated.equals(sd.getTypeName())) { + return true; + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + } catch (Exception e) { + return false; + } + } + private String hashTail(String type) { - return type.contains("#") ? "" : type.substring(type.lastIndexOf("/") + 1); + return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); + } + + + private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set elementDependencies, List paramTypes, boolean canBeNone) { + int i = 0; + for (ExpressionNode expr : exp.getParameters()) { + if (isExpressionParameter(exp, i)) { + paramTypes.add(executeType(changeThis(context, focus), focus, expr, elementDependencies, true, canBeNone, expr)); + } else { + paramTypes.add(executeType(context, context.thisItem, expr, elementDependencies, true, canBeNone, expr)); + } + i++; + } } @SuppressWarnings("unchecked") - private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) - throws PathEngineException, DefinitionException { + private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set elementDependencies, ExpressionNode container) throws PathEngineException, DefinitionException { List paramTypes = new ArrayList(); - if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) { + if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) { paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - } else { - int i = 0; - for (ExpressionNode expr : exp.getParameters()) { - if (isExpressionParameter(exp, i)) { - paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); - } else { - paramTypes.add(executeType(context, context.thisItem, expr, true)); + } else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) { + TypeDetails base = TypeDetails.empty(); + TypeDetails lFocus = focus; + boolean changed = false; + do { + evaluateParameters(context, lFocus, exp, elementDependencies, paramTypes, true); + changed = false; + if (!base.contains(paramTypes.get(0))) { + changed = true; + base.addTypes(paramTypes.get(0)); + lFocus = base; } - i++; + } while (changed); + paramTypes.clear(); + paramTypes.add(base); + } else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists || + exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue + || exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) { + evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false); + } else { + evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false); + } + if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) { + if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION)); + } } switch (exp.getFunction()) { - case Empty: + case Empty : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Not: + case Not : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Exists: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); + case Exists : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case SubsetOf: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case SubsetOf : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered()); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case SupersetOf: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case SupersetOf : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case IsDistinct: + case IsDistinct : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Distinct: + case Distinct : return focus; - case Count: + case Count : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); - case Where: - return focus; - case Select: - return anything(focus.getCollectionStatus()); - case All: + case Where : + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); + // special case: where the focus is Reference, and the parameter to where is resolve() "is", we will suck up the target types + if (focus.hasType("Reference")) { + boolean canRestrictTargets = !exp.getParameters().isEmpty(); + List targets = new ArrayList<>(); + if (canRestrictTargets) { + ExpressionNode p = exp.getParameters().get(0); + if (p.getKind() == Kind.Function && p.getName().equals("resolve") && p.getOperation() == Operation.Is) { + targets.add(p.getOpNext().getName()); + } else { + canRestrictTargets = false; + } + } + if (canRestrictTargets) { + TypeDetails td = focus.copy(); + td.getTargets().clear(); + td.getTargets().addAll(targets); + return td; + } else { + return focus; + } + } else { + return focus; + } + case Select : + return paramTypes.get(0); + case All : + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Repeat: + case Repeat : + return paramTypes.get(0); + case Aggregate : return anything(focus.getCollectionStatus()); - case Aggregate: - return anything(focus.getCollectionStatus()); - case Item: { + case Item : { checkOrdered(focus, "item", exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); - return focus; + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + return focus.toSingleton(); } - case As: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); + case As : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + String tn = checkType(focus, exp); + TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); + if (td.typesHaveTargets()) { + td.addTargets(focus.getTargets()); + } + return td; } - case OfType: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); + case OfType : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + String tn = checkType(focus, exp); + TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); + if (td.typesHaveTargets()) { + td.addTargets(focus.getTargets()); + } + return td; } - case Type: { + case Type : { boolean s = false; boolean c = false; for (ProfiledType pt : focus.getProfiledTypes()) { @@ -3350,50 +3461,47 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); } } - case Is: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Is : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case Single: + case Single : return focus.toSingleton(); - case First: { + case First : { checkOrdered(focus, "first", exp); return focus.toSingleton(); } - case Last: { + case Last : { checkOrdered(focus, "last", exp); return focus.toSingleton(); } - case Tail: { + case Tail : { checkOrdered(focus, "tail", exp); return focus; } - case Skip: { + case Skip : { checkOrdered(focus, "skip", exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); return focus; } - case Take: { + case Take : { checkOrdered(focus, "take", exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); return focus; } - case Union: { + case Union : { return focus.union(paramTypes.get(0)); } - case Combine: { + case Combine : { return focus.union(paramTypes.get(0)); } - case Intersect: { + case Intersect : { return focus.intersect(paramTypes.get(0)); } - case Exclude: { + case Exclude : { return focus; } - case Iif: { + case Iif : { TypeDetails types = new TypeDetails(null); checkSingleton(focus, "iif", exp); checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); @@ -3403,93 +3511,79 @@ public class FHIRPathEngine { } return types; } - case Lower: { + case Lower : { checkContextString(focus, "lower", exp, true); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case Upper: { + case Upper : { checkContextString(focus, "upper", exp, true); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case ToChars: { + case ToChars : { checkContextString(focus, "toChars", exp, true); - return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); + return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); } - case IndexOf: { + case IndexOf : { checkContextString(focus, "indexOf", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); } - case Substring: { + case Substring : { checkContextString(focus, "subString", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case StartsWith: { + case StartsWith : { checkContextString(focus, "startsWith", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case EndsWith: { + case EndsWith : { checkContextString(focus, "endsWith", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case Matches: { + case Matches : { checkContextString(focus, "matches", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case MatchesFull: { + case MatchesFull : { checkContextString(focus, "matches", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case ReplaceMatches: { + case ReplaceMatches : { checkContextString(focus, "replaceMatches", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case Contains: { + case Contains : { checkContextString(focus, "contains", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case Replace: { + case Replace : { checkContextString(focus, "replace", exp, true); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case Length: { + case Length : { checkContextPrimitive(focus, "length", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); } - case Children: + case Children : return childTypes(focus, "*", exp); - case Descendants: + case Descendants : return childTypes(focus, "**", exp); - case MemberOf: { + case MemberOf : { checkContextCoded(focus, "memberOf", exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case Trace: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return focus; + case Trace : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return focus; } case DefineVariable : { checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String)); @@ -3510,161 +3604,160 @@ public class FHIRPathEngine { } return focus; } - case Check: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return focus; + case Check : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return focus; } - case Today: + case Today : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); - case Now: + case Now : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); - case Resolve: { + case Resolve : { checkContextReference(focus, "resolve", exp); - return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); + return new TypeDetails(focus.getCollectionStatus(), "Resource"); } - case Extension: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); + case Extension : { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + ExpressionNode p = exp.getParameters().get(0); + if (p.getKind() == Kind.Constant && p.getConstant() != null) { + String url = exp.getParameters().get(0).getConstant().primitiveValue(); + ExtensionDefinition ed = findExtensionDefinition(focus, url); + if (ed != null) { + return new TypeDetails(CollectionStatus.ORDERED, new ProfiledType(ed.sd.getUrl())); + } else { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION)); + } + return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); + } } - case AnyTrue: + case AnyTrue: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case AllTrue: + case AllTrue: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case AnyFalse: + case AnyFalse: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case AllFalse: + case AllFalse: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case HasValue: + case HasValue : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case HtmlChecks1: + case HtmlChecks1 : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case HtmlChecks2: + case HtmlChecks2 : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Comparable: + case Comparable : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Encode: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Decode: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Escape: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Unescape: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Trim: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Split: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case Join: - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - case ToInteger: { + case ToInteger : { checkContextPrimitive(focus, "toInteger", true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); } - case ToDecimal: { + case ToDecimal : { checkContextPrimitive(focus, "toDecimal", true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); } - case ToString: { + case ToString : { checkContextPrimitive(focus, "toString", true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } - case ToQuantity: { + case ToQuantity : { checkContextPrimitive(focus, "toQuantity", true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); } - case ToBoolean: { + case ToBoolean : { checkContextPrimitive(focus, "toBoolean", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case ToDateTime: { + case ToDateTime : { checkContextPrimitive(focus, "ToDateTime", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); } - case ToTime: { + case ToTime : { checkContextPrimitive(focus, "ToTime", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); } - case ConvertsToString: - case ConvertsToQuantity: { + case ConvertsToString : + case ConvertsToQuantity :{ checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - } - case ConvertsToInteger: - case ConvertsToDecimal: - case ConvertsToDateTime: - case ConvertsToDate: - case ConvertsToTime: - case ConvertsToBoolean: { + } + case ConvertsToInteger : + case ConvertsToDecimal : + case ConvertsToDateTime : + case ConvertsToDate : + case ConvertsToTime : + case ConvertsToBoolean : { checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } case ConformsTo: { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } - case Abs: { + case Abs : { checkContextNumerical(focus, "abs", exp); - return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); + return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); } - case Truncate: - case Floor: - case Ceiling: { + case Truncate : + case Floor : + case Ceiling : { checkContextDecimal(focus, exp.getFunction().toCode(), exp); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); - } + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); + } - case Round: { + case Round :{ checkContextDecimal(focus, "round", exp); if (paramTypes.size() > 0) { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); } - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); - } + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); + } - case Exp: - case Ln: - case Sqrt: { - checkContextNumerical(focus, exp.getFunction().toCode(), exp); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); + case Exp : + case Ln : + case Sqrt : { + checkContextNumerical(focus, exp.getFunction().toCode(), exp); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); } - case Log: { - checkContextNumerical(focus, exp.getFunction().toCode(), exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); - return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); + case Log : { + checkContextNumerical(focus, exp.getFunction().toCode(), exp); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); } - case Power: { - checkContextNumerical(focus, exp.getFunction().toCode(), exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); - return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); + case Power : { + checkContextNumerical(focus, exp.getFunction().toCode(), exp); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); + return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); } case LowBoundary: case HighBoundary: { checkContextContinuous(focus, exp.getFunction().toCode(), exp, true); if (paramTypes.size() > 0) { - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, - new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); } if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); + } else if ((focus.hasType("time"))) { + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time, TypeDetails.FP_Time); } else if (focus.hasType("decimal") || focus.hasType("integer")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); } else { @@ -3675,9 +3768,12 @@ public class FHIRPathEngine { checkContextContinuous(focus, exp.getFunction().toCode(), exp, false); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); } - - case Custom: { - return hostServices.checkFunction(this, context.appInfo, exp.getName(), focus, paramTypes); + case hasTemplateIdOf: { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + } + case Custom : { + return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes); } default: break; @@ -3685,21 +3781,64 @@ public class FHIRPathEngine { throw new Error("not Implemented yet"); } + private ExtensionDefinition findExtensionDefinition(TypeDetails focus, String url) { + if (Utilities.isAbsoluteUrl(url)) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); + if (sd == null) { + return null; + } else { + return new ExtensionDefinition(true, sd, sd.getSnapshot().getElementFirstRep()); + } + } + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, focus.getType()); + if (sd != null) { + for (ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.hasFixed() && url.equals(ed.getFixed().primitiveValue())) { + return new ExtensionDefinition(false, sd, ed); + } + } + } + return null; + } + + private String checkType(TypeDetails focus, ExpressionNode exp) { + String tn; + if (exp.getParameters().get(0).getInner() != null) { + tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); + } else { + tn = "FHIR."+exp.getParameters().get(0).getName(); + } + if (tn.startsWith("System.")) { + tn = tn.substring(7); + } else if (tn.startsWith("FHIR.")) { + tn = Utilities.pathURL(Constants.NS_FHIR_ROOT, "StructureDefinition", tn.substring(5)); + } else if (tn.startsWith("CDA.")) { + tn = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); + } + + if (typeCastIsImpossible(focus, tn)) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE)); + } + return tn; + } + + private boolean typeCastIsImpossible(TypeDetails focus, String tn) { + return !focus.hasType(tn); + } + private boolean isExpressionParameter(ExpressionNode exp, int i) { switch (i) { case 0: - return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists - || exp.getFunction() == Function.All || exp.getFunction() == Function.Select - || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate; + return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate; case 1: return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable; - default: + default: return false; } } - private void checkParamTypes(ExpressionNode expr, String funcName, List paramTypes, - TypeDetails... typeSet) throws PathEngineException { + + private void checkParamTypes(ExpressionNode expr, String funcName,List paramTypes, TypeDetails... typeSet) throws PathEngineException { int i = 0; for (TypeDetails pt : typeSet) { if (i == paramTypes.size()) { @@ -3712,12 +3851,15 @@ public class FHIRPathEngine { throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString()); } } + if (actual.getCollectionStatus() != CollectionStatus.SINGLETON && pt.getCollectionStatus() == CollectionStatus.SINGLETON) { + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER, funcName, i, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER)); + } } } private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) { -// typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT)); + typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT)); } } @@ -3728,66 +3870,60 @@ public class FHIRPathEngine { } private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") - && !focus.hasType(worker, "canonical")) { + if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) { throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe()); } } + private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") - && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) { + if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) { throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe()); } } - private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) - throws PathEngineException { - if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") - && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) { - throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, - name, focus.describe()); + + private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException { + if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) { + throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe()); } } - private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) - throws PathEngineException { + + private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException { if (!focus.hasNoTypes()) { if (canQty) { if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) { - throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), - "Quantity, " + primitiveTypes.toString()); + throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString()); } } else if (!focus.hasType(primitiveTypes)) { - throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), - primitiveTypes.toString()); + throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString()); } } } private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) { + if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) { throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe()); - } + } } private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) { throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe()); - } + } } private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException { if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) { throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); - } + } } - private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) - throws PathEngineException, DefinitionException { + private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException { TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); for (String f : focus.getTypes()) { - getChildTypesByName(f, mask, result, expr); + getChildTypesByName(f, mask, result, expr, null, null); } return result; } @@ -3796,214 +3932,129 @@ public class FHIRPathEngine { return new TypeDetails(status, allTypes.keySet()); } - // private boolean isPrimitiveType(String s) { - // return s.equals("boolean") || s.equals("integer") || s.equals("decimal") || - // s.equals("base64Binary") || s.equals("instant") || s.equals("string") || - // s.equals("uri") || s.equals("date") || s.equals("dateTime") || - // s.equals("time") || s.equals("code") || s.equals("oid") || s.equals("id") || - // s.equals("unsignedInt") || s.equals("positiveInt") || s.equals("markdown"); - // } + // private boolean isPrimitiveType(String s) { + // return s.equals("boolean") || s.equals("integer") || s.equals("decimal") || s.equals("base64Binary") || s.equals("instant") || s.equals("string") || s.equals("uri") || s.equals("date") || s.equals("dateTime") || s.equals("time") || s.equals("code") || s.equals("oid") || s.equals("id") || s.equals("unsignedInt") || s.equals("positiveInt") || s.equals("markdown"); + // } - private List evaluateFunction(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List evaluateFunction(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { switch (exp.getFunction()) { - case Empty: - return funcEmpty(context, focus, exp); - case Not: - return funcNot(context, focus, exp); - case Exists: - return funcExists(context, focus, exp); - case SubsetOf: - return funcSubsetOf(context, focus, exp); - case SupersetOf: - return funcSupersetOf(context, focus, exp); - case IsDistinct: - return funcIsDistinct(context, focus, exp); - case Distinct: - return funcDistinct(context, focus, exp); - case Count: - return funcCount(context, focus, exp); - case Where: - return funcWhere(context, focus, exp); - case Select: - return funcSelect(context, focus, exp); - case All: - return funcAll(context, focus, exp); - case Repeat: - return funcRepeat(context, focus, exp); - case Aggregate: - return funcAggregate(context, focus, exp); - case Item: - return funcItem(context, focus, exp); - case As: - return funcAs(context, focus, exp); - case OfType: - return funcOfType(context, focus, exp); - case Type: - return funcType(context, focus, exp); - case Is: - return funcIs(context, focus, exp); - case Single: - return funcSingle(context, focus, exp); - case First: - return funcFirst(context, focus, exp); - case Last: - return funcLast(context, focus, exp); - case Tail: - return funcTail(context, focus, exp); - case Skip: - return funcSkip(context, focus, exp); - case Take: - return funcTake(context, focus, exp); - case Union: - return funcUnion(context, focus, exp); - case Combine: - return funcCombine(context, focus, exp); - case Intersect: - return funcIntersect(context, focus, exp); - case Exclude: - return funcExclude(context, focus, exp); - case Iif: - return funcIif(context, focus, exp); - case Lower: - return funcLower(context, focus, exp); - case Upper: - return funcUpper(context, focus, exp); - case ToChars: - return funcToChars(context, focus, exp); - case IndexOf: - return funcIndexOf(context, focus, exp); - case Substring: - return funcSubstring(context, focus, exp); - case StartsWith: - return funcStartsWith(context, focus, exp); - case EndsWith: - return funcEndsWith(context, focus, exp); - case Matches: - return funcMatches(context, focus, exp); - case MatchesFull: - return funcMatchesFull(context, focus, exp); - case ReplaceMatches: - return funcReplaceMatches(context, focus, exp); - case Contains: - return funcContains(context, focus, exp); - case Replace: - return funcReplace(context, focus, exp); - case Length: - return funcLength(context, focus, exp); - case Children: - return funcChildren(context, focus, exp); - case Descendants: - return funcDescendants(context, focus, exp); - case MemberOf: - return funcMemberOf(context, focus, exp); - case Trace: - return funcTrace(context, focus, exp); - case DefineVariable: - return funcDefineVariable(context, focus, exp); - case Check: - return funcCheck(context, focus, exp); - case Today: - return funcToday(context, focus, exp); - case Now: - return funcNow(context, focus, exp); - case Resolve: - return funcResolve(context, focus, exp); - case Extension: - return funcExtension(context, focus, exp); - case AnyFalse: - return funcAnyFalse(context, focus, exp); - case AllFalse: - return funcAllFalse(context, focus, exp); - case AnyTrue: - return funcAnyTrue(context, focus, exp); - case AllTrue: - return funcAllTrue(context, focus, exp); - case HasValue: - return funcHasValue(context, focus, exp); - case Encode: - return funcEncode(context, focus, exp); - case Decode: - return funcDecode(context, focus, exp); - case Escape: - return funcEscape(context, focus, exp); - case Unescape: - return funcUnescape(context, focus, exp); - case Trim: - return funcTrim(context, focus, exp); - case Split: - return funcSplit(context, focus, exp); - case Join: - return funcJoin(context, focus, exp); - case HtmlChecks1: - return funcHtmlChecks1(context, focus, exp); - case HtmlChecks2: - return funcHtmlChecks2(context, focus, exp); - case Comparable: - return funcComparable(context, focus, exp); - case ToInteger: - return funcToInteger(context, focus, exp); - case ToDecimal: - return funcToDecimal(context, focus, exp); - case ToString: - return funcToString(context, focus, exp); - case ToBoolean: - return funcToBoolean(context, focus, exp); - case ToQuantity: - return funcToQuantity(context, focus, exp); - case ToDateTime: - return funcToDateTime(context, focus, exp); - case ToTime: - return funcToTime(context, focus, exp); - case ConvertsToInteger: - return funcIsInteger(context, focus, exp); - case ConvertsToDecimal: - return funcIsDecimal(context, focus, exp); - case ConvertsToString: - return funcIsString(context, focus, exp); - case ConvertsToBoolean: - return funcIsBoolean(context, focus, exp); - case ConvertsToQuantity: - return funcIsQuantity(context, focus, exp); - case ConvertsToDateTime: - return funcIsDateTime(context, focus, exp); - case ConvertsToDate: - return funcIsDate(context, focus, exp); - case ConvertsToTime: - return funcIsTime(context, focus, exp); - case ConformsTo: - return funcConformsTo(context, focus, exp); - case Round: - return funcRound(context, focus, exp); - case Sqrt: - return funcSqrt(context, focus, exp); - case Abs: - return funcAbs(context, focus, exp); - case Ceiling: - return funcCeiling(context, focus, exp); - case Exp: - return funcExp(context, focus, exp); - case Floor: - return funcFloor(context, focus, exp); - case Ln: - return funcLn(context, focus, exp); - case Log: - return funcLog(context, focus, exp); - case Power: - return funcPower(context, focus, exp); - case Truncate: - return funcTruncate(context, focus, exp); - case LowBoundary: - return funcLowBoundary(context, focus, exp); - case HighBoundary: - return funcHighBoundary(context, focus, exp); - case Precision: - return funcPrecision(context, focus, exp); + case Empty : return funcEmpty(context, focus, exp); + case Not : return funcNot(context, focus, exp); + case Exists : return funcExists(context, focus, exp); + case SubsetOf : return funcSubsetOf(context, focus, exp); + case SupersetOf : return funcSupersetOf(context, focus, exp); + case IsDistinct : return funcIsDistinct(context, focus, exp); + case Distinct : return funcDistinct(context, focus, exp); + case Count : return funcCount(context, focus, exp); + case Where : return funcWhere(context, focus, exp); + case Select : return funcSelect(context, focus, exp); + case All : return funcAll(context, focus, exp); + case Repeat : return funcRepeat(context, focus, exp); + case Aggregate : return funcAggregate(context, focus, exp); + case Item : return funcItem(context, focus, exp); + case As : return funcAs(context, focus, exp); + case OfType : return funcOfType(context, focus, exp); + case Type : return funcType(context, focus, exp); + case Is : return funcIs(context, focus, exp); + case Single : return funcSingle(context, focus, exp); + case First : return funcFirst(context, focus, exp); + case Last : return funcLast(context, focus, exp); + case Tail : return funcTail(context, focus, exp); + case Skip : return funcSkip(context, focus, exp); + case Take : return funcTake(context, focus, exp); + case Union : return funcUnion(context, focus, exp); + case Combine : return funcCombine(context, focus, exp); + case Intersect : return funcIntersect(context, focus, exp); + case Exclude : return funcExclude(context, focus, exp); + case Iif : return funcIif(context, focus, exp); + case Lower : return funcLower(context, focus, exp); + case Upper : return funcUpper(context, focus, exp); + case ToChars : return funcToChars(context, focus, exp); + case IndexOf : return funcIndexOf(context, focus, exp); + case Substring : return funcSubstring(context, focus, exp); + case StartsWith : return funcStartsWith(context, focus, exp); + case EndsWith : return funcEndsWith(context, focus, exp); + case Matches : return funcMatches(context, focus, exp); + case MatchesFull : return funcMatchesFull(context, focus, exp); + case ReplaceMatches : return funcReplaceMatches(context, focus, exp); + case Contains : return funcContains(context, focus, exp); + case Replace : return funcReplace(context, focus, exp); + case Length : return funcLength(context, focus, exp); + case Children : return funcChildren(context, focus, exp); + case Descendants : return funcDescendants(context, focus, exp); + case MemberOf : return funcMemberOf(context, focus, exp); + case Trace : return funcTrace(context, focus, exp); + case DefineVariable : return funcDefineVariable(context, focus, exp); + case Check : return funcCheck(context, focus, exp); + case Today : return funcToday(context, focus, exp); + case Now : return funcNow(context, focus, exp); + case Resolve : return funcResolve(context, focus, exp); + case Extension : return funcExtension(context, focus, exp); + case AnyFalse: return funcAnyFalse(context, focus, exp); + case AllFalse: return funcAllFalse(context, focus, exp); + case AnyTrue: return funcAnyTrue(context, focus, exp); + case AllTrue: return funcAllTrue(context, focus, exp); + case HasValue : return funcHasValue(context, focus, exp); + case Encode : return funcEncode(context, focus, exp); + case Decode : return funcDecode(context, focus, exp); + case Escape : return funcEscape(context, focus, exp); + case Unescape : return funcUnescape(context, focus, exp); + case Trim : return funcTrim(context, focus, exp); + case Split : return funcSplit(context, focus, exp); + case Join : return funcJoin(context, focus, exp); + case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp); + case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); + case Comparable : return funcComparable(context, focus, exp); + case ToInteger : return funcToInteger(context, focus, exp); + case ToDecimal : return funcToDecimal(context, focus, exp); + case ToString : return funcToString(context, focus, exp); + case ToBoolean : return funcToBoolean(context, focus, exp); + case ToQuantity : return funcToQuantity(context, focus, exp); + case ToDateTime : return funcToDateTime(context, focus, exp); + case ToTime : return funcToTime(context, focus, exp); + case ConvertsToInteger : return funcIsInteger(context, focus, exp); + case ConvertsToDecimal : return funcIsDecimal(context, focus, exp); + case ConvertsToString : return funcIsString(context, focus, exp); + case ConvertsToBoolean : return funcIsBoolean(context, focus, exp); + case ConvertsToQuantity : return funcIsQuantity(context, focus, exp); + case ConvertsToDateTime : return funcIsDateTime(context, focus, exp); + case ConvertsToDate : return funcIsDate(context, focus, exp); + case ConvertsToTime : return funcIsTime(context, focus, exp); + case ConformsTo : return funcConformsTo(context, focus, exp); + case Round : return funcRound(context, focus, exp); + case Sqrt : return funcSqrt(context, focus, exp); + case Abs : return funcAbs(context, focus, exp); + case Ceiling : return funcCeiling(context, focus, exp); + case Exp : return funcExp(context, focus, exp); + case Floor : return funcFloor(context, focus, exp); + case Ln : return funcLn(context, focus, exp); + case Log : return funcLog(context, focus, exp); + case Power : return funcPower(context, focus, exp); + case Truncate : return funcTruncate(context, focus, exp); + case LowBoundary : return funcLowBoundary(context, focus, exp); + case HighBoundary : return funcHighBoundary(context, focus, exp); + case Precision : return funcPrecision(context, focus, exp); + case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp); - case Custom: { + + case Custom: { List> params = new ArrayList>(); - for (ExpressionNode p : exp.getParameters()) { - params.add(execute(context, focus, p, true)); + if (hostServices.paramIsType( exp.getName(), 0)) { + if (exp.getParameters().size() > 0) { + String tn; + if (exp.getParameters().get(0).getInner() != null) { + tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); + } else { + tn = "FHIR."+exp.getParameters().get(0).getName(); + } + List p = new ArrayList<>(); + p.add(new CodeType(tn)); + params.add(p); + } + } else { + for (ExpressionNode p : exp.getParameters()) { + params.add(execute(context, focus, p, true)); + } } return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params); } @@ -4012,6 +4063,53 @@ public class FHIRPathEngine { } } + private List funcHasTemplateIdOf(ExecutionContext context, List focus, ExpressionNode exp) { + List result = new ArrayList(); + List swb = execute(context, focus, exp.getParameters().get(0), true); + String sw = convertToString(swb); + + StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw); + if (focus.size() == 1 && sd != null) { + boolean found = false; + for (Identifier id : sd.getIdentifier()) { + if (id.getValue().startsWith("urn:hl7ii:")) { + String[] p = id.getValue().split("\\:"); + if (p.length == 4) { + found = found || hasTemplateId(focus.get(0), p[2], p[3]); + } + } else if (id.getValue().startsWith("urn:oid:")) { + found = found || hasTemplateId(focus.get(0), id.getValue().substring(8)); + } + } + result.add(new BooleanType(found)); + } + return result; + } + + private boolean hasTemplateId(Base base, String rv) { + List templateIds = base.listChildrenByName("templateId"); + for (Base templateId : templateIds) { + Base root = templateId.getChildValueByName("root"); + Base extension = templateId.getChildValueByName("extension"); + if (extension == null && root != null && rv.equals(root.primitiveValue())) { + return true; + } + } + return false; + } + + private boolean hasTemplateId(Base base, String rv, String ev) { + List templateIds = base.listChildrenByName("templateId"); + for (Base templateId : templateIds) { + Base root = templateId.getChildValueByName("root"); + Base extension = templateId.getChildValueByName("extension"); + if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) { + return true; + } + } + return false; + } + private List funcSqrt(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); @@ -4026,12 +4124,12 @@ public class FHIRPathEngine { // just return nothing } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); } return result; } + private List funcAbs(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size()); @@ -4049,12 +4147,12 @@ public class FHIRPathEngine { Quantity qty = (Quantity) base; result.add(qty.copy().setValue(qty.getValue().abs())); } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal"); } return result; } + private List funcCeiling(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size()); @@ -4063,14 +4161,12 @@ public class FHIRPathEngine { List result = new ArrayList(); if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { Double d = Double.parseDouble(base.primitiveValue()); - try { - result.add(new IntegerType((int) Math.ceil(d))); + try {result.add(new IntegerType((int) Math.ceil(d))); } catch (Exception e) { // just return nothing } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal"); } return result; } @@ -4089,12 +4185,12 @@ public class FHIRPathEngine { // just return nothing } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal"); } return result; } + private List funcExp(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() == 0) { return new ArrayList(); @@ -4113,12 +4209,12 @@ public class FHIRPathEngine { } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal"); } - return result; + return result; } + private List funcLn(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size()); @@ -4131,14 +4227,14 @@ public class FHIRPathEngine { result.add(new DecimalType(Math.log(d))); } catch (Exception e) { // just return nothing - } + } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal"); } return result; } + private List funcLog(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size()); @@ -4148,8 +4244,7 @@ public class FHIRPathEngine { if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { List n1 = execute(context, focus, expr.getParameters().get(0), true); if (n1.size() != 1) { - throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", - "integer or decimal"); + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal"); } Double e = Double.parseDouble(n1.get(0).primitiveValue()); Double d = Double.parseDouble(base.primitiveValue()); @@ -4159,8 +4254,7 @@ public class FHIRPathEngine { // just return nothing } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal"); } return result; } @@ -4178,8 +4272,7 @@ public class FHIRPathEngine { if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { List n1 = execute(context, focus, expr.getParameters().get(0), true); if (n1.size() != 1) { - throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", - "integer or decimal"); + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal"); } Double e = Double.parseDouble(n1.get(0).primitiveValue()); Double d = Double.parseDouble(base.primitiveValue()); @@ -4189,8 +4282,7 @@ public class FHIRPathEngine { // just return nothing } } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal"); } return result; } @@ -4208,8 +4300,7 @@ public class FHIRPathEngine { } result.add(new IntegerType(s)); } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); } return result; } @@ -4315,8 +4406,7 @@ public class FHIRPathEngine { } else if (base.hasType("time")) { result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue()))); } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), - "decimal or date"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); } return result; } @@ -4332,22 +4422,20 @@ public class FHIRPathEngine { if (expr.getParameters().size() == 1) { List n1 = execute(context, focus, expr.getParameters().get(0), true); if (n1.size() != 1) { - throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", - "integer"); + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer"); } i = Integer.parseInt(n1.get(0).primitiveValue()); } - BigDecimal d = new BigDecimal(base.primitiveValue()); + BigDecimal d = new BigDecimal (base.primitiveValue()); result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP))); } else { - makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), - "integer or decimal"); + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal"); } return result; } private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - + private ContextUtilities cu; public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { @@ -4362,7 +4450,7 @@ public class FHIRPathEngine { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } @@ -4376,7 +4464,7 @@ public class FHIRPathEngine { if (focus.size() == 1) { String cnt = focus.get(0).primitiveValue(); if ("hex".equals(param)) { - result.add(new StringType(bytesToHex(cnt.getBytes()))); + result.add(new StringType(bytesToHex(cnt.getBytes()))); } else if ("base64".equals(param)) { Base64.Encoder enc = Base64.getEncoder(); result.add(new StringType(enc.encodeToString(cnt.getBytes()))); @@ -4385,7 +4473,7 @@ public class FHIRPathEngine { result.add(new StringType(enc.encodeToString(cnt.getBytes()))); } } - return result; + return result; } private List funcDecode(ExecutionContext context, List focus, ExpressionNode exp) { @@ -4393,11 +4481,10 @@ public class FHIRPathEngine { String param = nl.get(0).primitiveValue(); List result = new ArrayList(); - if (focus.size() == 1) { String cnt = focus.get(0).primitiveValue(); if ("hex".equals(param)) { - result.add(new StringType(new String(hexStringToByteArray(cnt)))); + result.add(new StringType(new String(hexStringToByteArray(cnt)))); } else if ("base64".equals(param)) { Base64.Decoder enc = Base64.getDecoder(); result.add(new StringType(new String(enc.decode(cnt)))); @@ -4406,8 +4493,7 @@ public class FHIRPathEngine { result.add(new StringType(new String(enc.decode(cnt)))); } } - - return result; + return result; } private List funcEscape(ExecutionContext context, List focus, ExpressionNode exp) { @@ -4418,13 +4504,17 @@ public class FHIRPathEngine { if (focus.size() == 1) { String cnt = focus.get(0).primitiveValue(); if ("html".equals(param)) { - result.add(new StringType(Utilities.escapeXml(cnt))); + result.add(new StringType(Utilities.escapeXml(cnt))); } else if ("json".equals(param)) { - result.add(new StringType(Utilities.escapeJson(cnt))); + result.add(new StringType(Utilities.escapeJson(cnt))); + } else if ("url".equals(param)) { + result.add(new StringType(Utilities.URLEncode(cnt))); + } else if ("md".equals(param)) { + result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt))); } } - return result; + return result; } private List funcUnescape(ExecutionContext context, List focus, ExpressionNode exp) { @@ -4435,13 +4525,17 @@ public class FHIRPathEngine { if (focus.size() == 1) { String cnt = focus.get(0).primitiveValue(); if ("html".equals(param)) { - result.add(new StringType(Utilities.unescapeXml(cnt))); + result.add(new StringType(Utilities.unescapeXml(cnt))); } else if ("json".equals(param)) { - result.add(new StringType(Utilities.unescapeJson(cnt))); + result.add(new StringType(Utilities.unescapeJson(cnt))); + } else if ("url".equals(param)) { + result.add(new StringType(Utilities.URLDecode(cnt))); + } else if ("md".equals(param)) { + result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt))); } } - return result; + return result; } private List funcTrim(ExecutionContext context, List focus, ExpressionNode exp) { @@ -4450,7 +4544,7 @@ public class FHIRPathEngine { String cnt = focus.get(0).primitiveValue(); result.add(new StringType(cnt.trim())); } - return result; + return result; } private List funcSplit(ExecutionContext context, List focus, ExpressionNode exp) { @@ -4465,56 +4559,61 @@ public class FHIRPathEngine { result.add(new StringType(s)); } } - return result; + return result; } private List funcJoin(ExecutionContext context, List focus, ExpressionNode exp) { - List nl = execute(context, focus, exp.getParameters().get(0), true); - String param = nl.get(0).primitiveValue(); - String param2 = param; - if (exp.getParameters().size() == 2) { - nl = execute(context, focus, exp.getParameters().get(1), true); - param2 = nl.get(0).primitiveValue(); + List nl = exp.getParameters().size() > 0 ? execute(context, focus, exp.getParameters().get(0), true) : new ArrayList(); + String param = ""; + String param2 = ""; + if (exp.getParameters().size() > 0) { + param = nl.get(0).primitiveValue(); + param2 = param; + if (exp.getParameters().size() == 2) { + nl = execute(context, focus, exp.getParameters().get(1), true); + param2 = nl.get(0).primitiveValue(); + } } - + List result = new ArrayList(); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2); for (Base i : focus) { - b.append(i.primitiveValue()); + b.append(i.primitiveValue()); } result.add(new StringType(b.toString())); - return result; + return result; } - private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML if (focus.size() != 1) { - return makeBoolean(false); + return makeBoolean(false); } XhtmlNode x = focus.get(0).getXhtml(); if (x == null) { - return makeBoolean(false); + return makeBoolean(false); } - return makeBoolean(checkHtmlNames(x)); + boolean ok = checkHtmlNames(x, true); + if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) { + ok = checkForContent(x); + } + return makeBoolean(ok); } - private List funcHtmlChecks2(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcHtmlChecks2(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML if (focus.size() != 1) { - return makeBoolean(false); + return makeBoolean(false); } XhtmlNode x = focus.get(0).getXhtml(); if (x == null) { - return makeBoolean(false); + return makeBoolean(false); } - return makeBoolean(checkForContent(x)); + return makeBoolean(checkForContent(x)); } private boolean checkForContent(XhtmlNode x) { - if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) - || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) { + if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) { return true; } for (XhtmlNode c : x.getChildNodes()) { @@ -4525,25 +4624,24 @@ public class FHIRPathEngine { return false; } - private List funcComparable(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcComparable(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) { - return makeBoolean(false); + return makeBoolean(false); } List nl = execute(context, focus, exp.getParameters().get(0), true); if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) { - return makeBoolean(false); + return makeBoolean(false); } String s1 = getNamedValue(focus.get(0), "system"); String u1 = getNamedValue(focus.get(0), "code"); String s2 = getNamedValue(nl.get(0), "system"); String u2 = getNamedValue(nl.get(0), "code"); - + if (s1 == null || s2 == null || !s1.equals(s2)) { - return makeBoolean(false); + return makeBoolean(false); } if (u1 == null || u2 == null) { - return makeBoolean(false); + return makeBoolean(false); } if (u1.equals(u2)) { return makeBoolean(true); @@ -4552,13 +4650,14 @@ public class FHIRPathEngine { try { return makeBoolean(worker.getUcumService().isComparable(u1, u2)); } catch (UcumException e) { - return makeBoolean(false); - } + return makeBoolean(false); + } } else { - return makeBoolean(false); + return makeBoolean(false); } } + private String getNamedValue(Base base, String name) { Property p = base.getChildByName(name); if (p.hasValues() && p.getValues().size() == 1) { @@ -4567,38 +4666,44 @@ public class FHIRPathEngine { return null; } - private boolean checkHtmlNames(XhtmlNode node) { + private boolean checkHtmlNames(XhtmlNode node, boolean block) { if (node.getNodeType() == NodeType.Comment) { if (node.getContent().startsWith("DOCTYPE")) return false; } if (node.getNodeType() == NodeType.Element) { - if (!Utilities.existsInList(node.getName(), "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", - "b", "em", "i", "strong", "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", - "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", "ul", "ol", "li", "dl", "dt", "dd", "pre", - "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", "code", "samp", "img", - "map", "area")) { - return false; + if (block) { + if (!Utilities.existsInList(node.getName(), + "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", + "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", + "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", + "code", "samp", "img", "map", "area")) { + return false; + } + } else { + if (!Utilities.existsInList(node.getName(), + "a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) { + return false; + } } for (String an : node.getAttributes().keySet()) { - boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, "title", "style", "class", "id", "idref", "lang", - "xml:lang", "dir", "accesskey", "tabindex", + boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, + "title", "style", "class", "id", "idref", "lang", "xml:lang", "xml:space", "dir", "accesskey", "tabindex", // tables - "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", - "colspan") || + "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || - Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", - "blockquote.cite", "q.cite", "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", - "a.shape", "a.coords", "img.src", "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", - "img.ismap", "map.name", "area.shape", "area.coords", "area.href", "area.nohref", "area.alt", - "table.summary", "table.width", "table.border", "table.frame", "table.rules", "table.cellspacing", - "table.cellpadding", "pre.space", "td.nowrap"); + Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", + "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", + "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", + "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", + "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" + ); if (!ok) { return false; } } for (XhtmlNode c : node.getChildNodes()) { - if (!checkHtmlNames(c)) { + if (!checkHtmlNames(c, block && !"p".equals(c))) { return false; } } @@ -4635,9 +4740,9 @@ public class FHIRPathEngine { return result; } + private ExecutionContext changeThis(ExecutionContext context, Base newThis) { - ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, - newThis); + ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis); // append all of the defined variables from the context into the new context if (context.definedVariables != null) { for (String s : context.definedVariables.keySet()) { @@ -4688,12 +4793,14 @@ public class FHIRPathEngine { return result; } + private List funcToday(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); return result; } + private List funcMemberOf(ExecutionContext context, List focus, ExpressionNode exp) { List nl = execute(context, focus, exp.getParameters().get(0), true); if (nl.size() != 1 || focus.size() != 1) { @@ -4701,27 +4808,25 @@ public class FHIRPathEngine { } String url = nl.get(0).primitiveValue(); - ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) - : worker.fetchResource(ValueSet.class, url); + ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.fetchResource(ValueSet.class, url); if (vs == null) { return new ArrayList(); } Base l = focus.get(0); if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { - return makeBoolean( - worker.validateCode(terminologyServiceOptions.withGuessSystem(), l.castToCoding(l), vs).isOk()); + return makeBoolean(worker.validateCode(terminologyServiceOptions.withGuessSystem(), l.castToCoding(l), vs).isOk()); } else if (l.fhirType().equals("Coding")) { return makeBoolean(worker.validateCode(terminologyServiceOptions, l.castToCoding(l), vs).isOk()); } else if (l.fhirType().equals("CodeableConcept")) { return makeBoolean(worker.validateCode(terminologyServiceOptions, l.castToCodeableConcept(l), vs).isOk()); } else { - // System.out.println("unknown type in funcMemberOf: "+l.fhirType()); + // System.out.println("unknown type in funcMemberOf: "+l.fhirType()); return new ArrayList(); } } - private List funcDescendants(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + + private List funcDescendants(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List current = new ArrayList(); current.addAll(focus); @@ -4740,6 +4845,7 @@ public class FHIRPathEngine { return result; } + private List funcChildren(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); for (Base b : focus) { @@ -4748,8 +4854,8 @@ public class FHIRPathEngine { return result; } - private List funcReplace(ExecutionContext context, List focus, ExpressionNode expr) - throws FHIRException, PathEngineException { + + private List funcReplace(ExecutionContext context, List focus, ExpressionNode expr) throws FHIRException, PathEngineException { List result = new ArrayList(); List tB = execute(context, focus, expr.getParameters().get(0), true); String t = convertToString(tB); @@ -4774,8 +4880,8 @@ public class FHIRPathEngine { return result; } - private List funcReplaceMatches(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + + private List funcReplaceMatches(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List regexB = execute(context, focus, exp.getParameters().get(0), true); String regex = convertToString(regexB); @@ -4794,6 +4900,7 @@ public class FHIRPathEngine { return result; } + private List funcEndsWith(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List swb = execute(context, focus, exp.getParameters().get(0), true); @@ -4815,6 +4922,7 @@ public class FHIRPathEngine { return result; } + private List funcToString(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); result.add(new StringType(convertToString(focus)).noExtensions()); @@ -4843,7 +4951,7 @@ public class FHIRPathEngine { if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) { result.add(new BooleanType(true).noExtensions()); } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) { - result.add(new BooleanType(false).noExtensions()); + result.add(new BooleanType(false).noExtensions()); } } } @@ -4861,30 +4969,29 @@ public class FHIRPathEngine { result.add(q.noExtensions()); } } else if (focus.get(0) instanceof IntegerType) { - result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())) - .setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); + result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); } else if (focus.get(0) instanceof DecimalType) { - result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())) - .setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); + result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); } } return result; } private List funcToDateTime(ExecutionContext context, List focus, ExpressionNode expr) { - // List result = new ArrayList(); - // result.add(new BooleanType(convertToBoolean(focus))); - // return result; + // List result = new ArrayList(); + // result.add(new BooleanType(convertToBoolean(focus))); + // return result; throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime"); } private List funcToTime(ExecutionContext context, List focus, ExpressionNode expr) { - // List result = new ArrayList(); - // result.add(new BooleanType(convertToBoolean(focus))); - // return result; + // List result = new ArrayList(); + // result.add(new BooleanType(convertToBoolean(focus))); + // return result; throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime"); } + private List funcToDecimal(ExecutionContext context, List focus, ExpressionNode expr) { String s = convertToString(focus); List result = new ArrayList(); @@ -4900,6 +5007,7 @@ public class FHIRPathEngine { return result; } + private List funcIif(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { if (focus.size() > 1) { throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size()); @@ -4915,7 +5023,8 @@ public class FHIRPathEngine { return execute(context, focus, exp.getParameters().get(2), true); } } - + + private List funcTake(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List n1 = execute(context, focus, exp.getParameters().get(0), true); int i1 = Integer.parseInt(n1.get(0).primitiveValue()); @@ -4927,6 +5036,7 @@ public class FHIRPathEngine { return result; } + private List funcUnion(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); for (Base item : focus) { @@ -4953,17 +5063,16 @@ public class FHIRPathEngine { return result; } - private List funcIntersect(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcIntersect(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - List other = execute(context, focus, exp.getParameters().get(0), true); + List other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); for (Base item : focus) { if (!doContains(result, item) && doContains(other, item)) { result.add(item); } } - return result; + return result; } private List funcExclude(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { @@ -4978,16 +5087,16 @@ public class FHIRPathEngine { return result; } - private List funcSingle(ExecutionContext context, List focus, ExpressionNode expr) - throws PathEngineException { + + private List funcSingle(ExecutionContext context, List focus, ExpressionNode expr) throws PathEngineException { if (focus.size() == 1) { return focus; } throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size()); } - private List funcIs(ExecutionContext context, List focus, ExpressionNode expr) - throws PathEngineException { + + private List funcIs(ExecutionContext context, List focus, ExpressionNode expr) throws PathEngineException { if (focus.size() == 0 || focus.size() > 1) { return makeNull(); } @@ -5004,13 +5113,12 @@ public class FHIRPathEngine { } ns = texp.getName(); n = texp.getInner().getName(); - } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", - "Time", "SimpleTypeInfo", "ClassInfo")) { + } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) { ns = "System"; n = texp.getName(); } else { ns = "FHIR"; - n = texp.getName(); + n = texp.getName(); } if (ns.equals("System")) { if (focus.get(0) instanceof Resource) { @@ -5023,7 +5131,7 @@ public class FHIRPathEngine { } if ("Date".equals(t) && n.equals("DateTime")) { return makeBoolean(true); - } else { + } else { return makeBoolean(false); } } else { @@ -5038,33 +5146,34 @@ public class FHIRPathEngine { if (n.equals(sd.getType())) { return makeBoolean(true); } - sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); } return makeBoolean(false); } - } else { + } else { return makeBoolean(false); } } + private List funcAs(ExecutionContext context, List focus, ExpressionNode expr) { List result = new ArrayList(); String tn; if (expr.getParameters().get(0).getInner() != null) { - tn = expr.getParameters().get(0).getName() + "." + expr.getParameters().get(0).getInner().getName(); + tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); } else { - tn = "FHIR." + expr.getParameters().get(0).getName(); + tn = "FHIR."+expr.getParameters().get(0).getName(); } if (!isKnownType(tn)) { - throw new PathEngineException("The type " + tn + " is not valid"); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); } if (!doNotEnforceAsSingletonRule && focus.size() > 1) { - throw new PathEngineException("Attempt to use as() on more than one item (" + focus.size() + ")"); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); } - + for (Base b : focus) { if (tn.startsWith("System.")) { - if (b instanceof Element && ((Element) b).isDisallowExtensions()) { + if (b instanceof Element &&((Element) b).isDisallowExtensions()) { if (b.hasType(tn.substring(7))) { result.add(b); } @@ -5073,7 +5182,7 @@ public class FHIRPathEngine { } else if (tn.startsWith("FHIR.")) { String tnp = tn.substring(5); if (b.fhirType().equals(tnp)) { - result.add(b); + result.add(b); } else { StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); while (sd != null) { @@ -5081,30 +5190,31 @@ public class FHIRPathEngine { result.add(b); break; } - sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null - : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); } } } } return result; } + private List funcOfType(ExecutionContext context, List focus, ExpressionNode expr) { List result = new ArrayList(); String tn; if (expr.getParameters().get(0).getInner() != null) { - tn = expr.getParameters().get(0).getName() + "." + expr.getParameters().get(0).getInner().getName(); + tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); } else { - tn = "FHIR." + expr.getParameters().get(0).getName(); + tn = "FHIR."+expr.getParameters().get(0).getName(); } if (!isKnownType(tn)) { - throw new PathEngineException("The type " + tn + " is not valid"); + throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); } + for (Base b : focus) { if (tn.startsWith("System.")) { - if (b instanceof Element && ((Element) b).isDisallowExtensions()) { + if (b instanceof Element &&((Element) b).isDisallowExtensions()) { if (b.hasType(tn.substring(7))) { result.add(b); } @@ -5113,7 +5223,7 @@ public class FHIRPathEngine { } else if (tn.startsWith("FHIR.")) { String tnp = tn.substring(5); if (b.fhirType().equals(tnp)) { - result.add(b); + result.add(b); } else { StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); while (sd != null) { @@ -5121,8 +5231,21 @@ public class FHIRPathEngine { result.add(b); break; } - sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null - : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); + } + } + } else if (tn.startsWith("CDA.")) { + String tnp = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); + if (b.fhirType().equals(tnp)) { + result.add(b); + } else { + StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); + while (sd != null) { + if (tnp.equals(sd.getType())) { + result.add(b); + break; + } + sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); } } } @@ -5138,6 +5261,7 @@ public class FHIRPathEngine { return result; } + private List funcRepeat(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List current = new ArrayList(); @@ -5171,8 +5295,8 @@ public class FHIRPathEngine { return result; } - private List funcAggregate(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + + private List funcAggregate(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List total = new ArrayList(); if (exp.parameterCount() > 1) { total = execute(context, focus, exp.getParameters().get(1), false); @@ -5188,6 +5312,8 @@ public class FHIRPathEngine { return total; } + + private List funcIsDistinct(ExecutionContext context, List focus, ExpressionNode exp) { if (focus.size() < 1) { return makeBoolean(true); @@ -5198,7 +5324,7 @@ public class FHIRPathEngine { boolean distinct = true; for (int i = 0; i < focus.size(); i++) { - for (int j = i + 1; j < focus.size(); j++) { + for (int j = i+1; j < focus.size(); j++) { Boolean eq = doEquals(focus.get(j), focus.get(i)); if (eq == null) { return new ArrayList(); @@ -5211,8 +5337,8 @@ public class FHIRPathEngine { return makeBoolean(distinct); } - private List funcSupersetOf(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + + private List funcSupersetOf(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List target = execute(context, focus, exp.getParameters().get(0), true); boolean valid = true; @@ -5234,6 +5360,7 @@ public class FHIRPathEngine { return result; } + private List funcSubsetOf(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List target = execute(context, focus, exp.getParameters().get(0), true); @@ -5256,6 +5383,7 @@ public class FHIRPathEngine { return result; } + private List funcExists(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); boolean empty = true; @@ -5276,6 +5404,7 @@ public class FHIRPathEngine { return result; } + private List funcResolve(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); Base refContext = null; @@ -5287,8 +5416,7 @@ public class FHIRPathEngine { if (p != null && p.hasValues()) { s = convertToString(p.getValues().get(0)); } else { - s = null; // a reference without any valid actual reference (just identifier or display, - // but we can't resolve it) + s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) } } if (item.fhirType().equals("canonical")) { @@ -5298,10 +5426,11 @@ public class FHIRPathEngine { if (s != null) { Base res = null; if (s.startsWith("#")) { + String t = s.substring(1); Property p = context.rootResource.getChildByName("contained"); if (p != null) { for (Base c : p.getValues()) { - if (chompHash(s).equals(chompHash(c.getIdBase()))) { + if (t.equals(c.getIdBase())) { res = c; break; } @@ -5323,19 +5452,7 @@ public class FHIRPathEngine { return result; } - /** - * Strips a leading hashmark (#) if present at the start of a string - */ - private String chompHash(String theId) { - String retVal = theId; - while (retVal.startsWith("#")) { - retVal = retVal.substring(1); - } - return retVal; - } - - private List funcExtension(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcExtension(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List nl = execute(context, focus, exp.getParameters().get(0), true); String url = nl.get(0).primitiveValue(); @@ -5371,11 +5488,11 @@ public class FHIRPathEngine { } } result.add(new BooleanType(all).noExtensions()); - } else { + } else { boolean all = true; for (Base item : focus) { if (!canConvertToBoolean(item)) { - throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); } Equality v = asBool(item, true); @@ -5409,7 +5526,7 @@ public class FHIRPathEngine { boolean any = false; for (Base item : focus) { if (!canConvertToBoolean(item)) { - throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); } Equality v = asBool(item, true); @@ -5439,11 +5556,11 @@ public class FHIRPathEngine { } } result.add(new BooleanType(all).noExtensions()); - } else { + } else { boolean all = true; for (Base item : focus) { if (!canConvertToBoolean(item)) { - throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); } Equality v = asBool(item, true); if (v != Equality.True) { @@ -5476,7 +5593,7 @@ public class FHIRPathEngine { boolean any = false; for (Base item : focus) { if (!canConvertToBoolean(item)) { - throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); } Equality v = asBool(item, true); @@ -5500,7 +5617,7 @@ public class FHIRPathEngine { if (exp.getParameters().size() == 2) { List n2 = execute(context, focus, exp.getParameters().get(1), true); log(name, n2); - } else { + } else { log(name, focus); } return focus; @@ -5538,7 +5655,7 @@ public class FHIRPathEngine { List result = new ArrayList(); for (int i = 0; i < focus.size(); i++) { boolean found = false; - for (int j = i + 1; j < focus.size(); j++) { + for (int j = i+1; j < focus.size(); j++) { Boolean eq = doEquals(focus.get(j), focus.get(i)); if (eq == null) return new ArrayList(); @@ -5579,8 +5696,7 @@ public class FHIRPathEngine { return result; } - private List funcMatchesFull(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcMatchesFull(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); @@ -5610,7 +5726,7 @@ public class FHIRPathEngine { if (focus.size() != 1) { // } else if (swb.size() != 1) { - // + // } else if (Utilities.noString(sw)) { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { @@ -5620,7 +5736,7 @@ public class FHIRPathEngine { } else { result.add(new BooleanType(st.contains(sw)).noExtensions()); } - } + } return result; } @@ -5650,8 +5766,7 @@ public class FHIRPathEngine { return result; } - private List funcStartsWith(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcStartsWith(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List swb = execute(context, focus, exp.getParameters().get(0), true); String sw = convertToString(swb); @@ -5677,7 +5792,7 @@ public class FHIRPathEngine { List result = new ArrayList(); if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { String s = convertToString(focus.get(0)); - if (!Utilities.noString(s)) { + if (!Utilities.noString(s)) { result.add(new StringType(s.toLowerCase()).noExtensions()); } } @@ -5688,7 +5803,7 @@ public class FHIRPathEngine { List result = new ArrayList(); if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { String s = convertToString(focus.get(0)); - if (!Utilities.noString(s)) { + if (!Utilities.noString(s)) { result.add(new StringType(s.toUpperCase()).noExtensions()); } } @@ -5699,7 +5814,7 @@ public class FHIRPathEngine { List result = new ArrayList(); if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { String s = convertToString(focus.get(0)); - for (char c : s.toCharArray()) { + for (char c : s.toCharArray()) { result.add(new StringType(String.valueOf(c)).noExtensions()); } } @@ -5728,15 +5843,14 @@ public class FHIRPathEngine { return result; } - private List funcSubstring(ExecutionContext context, List focus, ExpressionNode exp) - throws FHIRException { + private List funcSubstring(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List n1 = execute(context, focus, exp.getParameters().get(0), true); int i1 = Integer.parseInt(n1.get(0).primitiveValue()); int i2 = -1; if (exp.parameterCount() == 2) { List n2 = execute(context, focus, exp.getParameters().get(1), true); - if (n2.isEmpty() || !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) { + if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) { return new ArrayList(); } i2 = Integer.parseInt(n2.get(0).primitiveValue()); @@ -5749,11 +5863,11 @@ public class FHIRPathEngine { return new ArrayList(); } if (exp.parameterCount() == 2) { - s = sw.substring(i1, Math.min(sw.length(), i1 + i2)); + s = sw.substring(i1, Math.min(sw.length(), i1+i2)); } else { s = sw.substring(i1); } - if (!Utilities.noString(s)) { + if (!Utilities.noString(s)) { result.add(new StringType(s).noExtensions()); } } @@ -5783,7 +5897,7 @@ public class FHIRPathEngine { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof StringType) { result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); - } else { + } else { result.add(new BooleanType(false).noExtensions()); } return result; @@ -5794,18 +5908,14 @@ public class FHIRPathEngine { if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); } else if (focus.get(0) instanceof IntegerType) { - result.add( - new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1) - .noExtensions()); + result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); } else if (focus.get(0) instanceof DecimalType) { - result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 - || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); + result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); } else if (focus.get(0) instanceof BooleanType) { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof StringType) { - result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")) - .noExtensions()); - } else { + result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); + } else { result.add(new BooleanType(false).noExtensions()); } return result; @@ -5818,10 +5928,9 @@ public class FHIRPathEngine { } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof StringType) { - result.add(new BooleanType((convertToString(focus.get(0)).matches( - "([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))) - .noExtensions()); - } else { + result.add(new BooleanType((convertToString(focus.get(0)).matches + ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions()); + } else { result.add(new BooleanType(false).noExtensions()); } return result; @@ -5834,17 +5943,15 @@ public class FHIRPathEngine { } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof StringType) { - result.add(new BooleanType((convertToString(focus.get(0)).matches( - "([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))) - .noExtensions()); - } else { + result.add(new BooleanType((convertToString(focus.get(0)).matches + ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions()); + } else { result.add(new BooleanType(false).noExtensions()); } return result; } - private List funcConformsTo(ExecutionContext context, List focus, ExpressionNode expr) - throws FHIRException { + private List funcConformsTo(ExecutionContext context, List focus, ExpressionNode expr) throws FHIRException { if (hostServices == null) { throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo"); } @@ -5853,7 +5960,7 @@ public class FHIRPathEngine { result.add(new BooleanType(false).noExtensions()); } else { String url = convertToString(execute(context, focus, expr.getParameters().get(0), true)); - result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo, focus.get(0), url)).noExtensions()); + result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo, focus.get(0), url)).noExtensions()); } return result; } @@ -5865,9 +5972,8 @@ public class FHIRPathEngine { } else if (focus.get(0) instanceof TimeType) { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof StringType) { - result.add(new BooleanType((convertToString(focus.get(0)).matches( - "(T)?([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))) - .noExtensions()); + result.add(new BooleanType((convertToString(focus.get(0)).matches + ("(T)?([01][0-9]|2[0-3])(:[0-5][0-9](:([0-5][0-9]|60))?)?(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))).noExtensions()); } else { result.add(new BooleanType(false).noExtensions()); } @@ -5880,7 +5986,7 @@ public class FHIRPathEngine { result.add(new BooleanType(false).noExtensions()); } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) { result.add(new BooleanType(true).noExtensions()); - } else { + } else { result.add(new BooleanType(false).noExtensions()); } return result; @@ -5898,7 +6004,7 @@ public class FHIRPathEngine { result.add(new BooleanType(true).noExtensions()); } else if (focus.get(0) instanceof BooleanType) { result.add(new BooleanType(true).noExtensions()); - } else if (focus.get(0) instanceof StringType) { + } else if (focus.get(0) instanceof StringType) { Quantity q = parseQuantityString(focus.get(0).primitiveValue()); result.add(new BooleanType(q != null).noExtensions()); } else { @@ -5919,7 +6025,7 @@ public class FHIRPathEngine { return null; } if (s.startsWith("'") && s.endsWith("'")) { - return Quantity.fromUcum(v, s.substring(1, s.length() - 1)); + return Quantity.fromUcum(v, s.substring(1, s.length()-1)); } if (s.equals("year") || s.equals("years")) { return Quantity.fromUcum(v, "a"); @@ -5939,16 +6045,17 @@ public class FHIRPathEngine { return Quantity.fromUcum(v, "ms"); } else { return null; - } + } } else { if (Utilities.isDecimal(s, true)) { return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); } else { return null; - } + } } } + private List funcIsDecimal(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); if (focus.size() != 1) { @@ -5963,7 +6070,7 @@ public class FHIRPathEngine { result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); } else { result.add(new BooleanType(false).noExtensions()); - } + } return result; } @@ -5980,7 +6087,7 @@ public class FHIRPathEngine { List result = new ArrayList(); for (int i = i1; i < focus.size(); i++) { result.add(focus.get(i)); - } + } return result; } @@ -5988,15 +6095,15 @@ public class FHIRPathEngine { List result = new ArrayList(); for (int i = 1; i < focus.size(); i++) { result.add(focus.get(i)); - } + } return result; } private List funcLast(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); if (focus.size() > 0) { - result.add(focus.get(focus.size() - 1)); - } + result.add(focus.get(focus.size()-1)); + } return result; } @@ -6004,10 +6111,11 @@ public class FHIRPathEngine { List result = new ArrayList(); if (focus.size() > 0) { result.add(focus.get(0)); - } + } return result; } + private List funcWhere(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List pc = new ArrayList(); @@ -6017,7 +6125,7 @@ public class FHIRPathEngine { Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); if (v == Equality.True) { result.add(item); - } + } } return result; } @@ -6035,12 +6143,13 @@ public class FHIRPathEngine { return result; } + private List funcItem(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) { result.add(focus.get(Integer.parseInt(s))); - } + } return result; } @@ -6050,53 +6159,53 @@ public class FHIRPathEngine { return result; } - private List funcNot(ExecutionContext context, List focus, ExpressionNode exp) - throws PathEngineException { - List result = new ArrayList(); + private List funcNot(ExecutionContext context, List focus, ExpressionNode exp) throws PathEngineException { + List result = new ArrayList(); Equality v = asBool(focus, exp); if (v != Equality.Null) { result.add(new BooleanType(v != Equality.True)); - } + } return result; } private class ElementDefinitionMatch { private ElementDefinition definition; + private ElementDefinition sourceDefinition; // if there was a content reference private String fixedType; - public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { super(); this.definition = definition; this.fixedType = fixedType; } - public ElementDefinition getDefinition() { return definition; } - + public ElementDefinition getSourceDefinition() { + return sourceDefinition; + } public String getFixedType() { return fixedType; } } - private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr) - throws PathEngineException, DefinitionException { + private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, TypeDetails focus, Set elementDependencies) throws PathEngineException, DefinitionException { if (Utilities.noString(type)) { throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName"); - } + } if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) { return; - } - if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { - return; - } + } - if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { + if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { getSimpleTypeChildTypesByName(name, result); - } else if (type.equals(TypeDetails.FP_ClassInfo)) { + } else if (type.equals(TypeDetails.FP_ClassInfo)) { getClassInfoChildTypesByName(name, result); } else { + if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { + return; + } + String url = null; if (type.contains("#")) { url = type.substring(0, type.indexOf("#")); @@ -6104,110 +6213,162 @@ public class FHIRPathEngine { url = type; } String tail = ""; - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); + StructureDefinition sd = worker.fetchTypeDefinition(url); if (sd == null) { - throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName"); + sd = worker.fetchResource(StructureDefinition.class, url); + } + if (sd == null) { + if (url.startsWith(TypeDetails.FP_NS)) { + return; + } else { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName#1"); + } } List sdl = new ArrayList(); ElementDefinitionMatch m = null; - if (type.contains("#")) - m = getElementDefinition(sd, type.substring(type.indexOf("#") + 1), false, expr); + if (type.contains("#")) { + List list = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr); + m = list.size() == 1 ? list.get(0) : null; + } if (m != null && hasDataType(m.definition)) { - if (m.fixedType != null) { - StructureDefinition dt = worker.fetchResource(StructureDefinition.class, - ProfileUtilities.sdNs(m.fixedType, null)); + if (m.fixedType != null) { + StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd); if (dt == null) { - throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(m.fixedType, null), - "getChildTypesByName"); + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName#2"); } sdl.add(dt); } else for (TypeRefComponent t : m.definition.getType()) { - StructureDefinition dt = worker.fetchResource(StructureDefinition.class, - ProfileUtilities.sdNs(t.getCode(), null)); + StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null)); if (dt == null) { - throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), null), - "getChildTypesByName"); + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName#3"); } - addTypeAndDescendents(sdl, dt, worker.allStructures()); + addTypeAndDescendents(sdl, dt, cu.allStructures()); // also add any descendant types } } else { - addTypeAndDescendents(sdl, sd, worker.allStructures()); + addTypeAndDescendents(sdl, sd, cu.allStructures()); if (type.contains("#")) { - tail = type.substring(type.indexOf("#") + 1); - tail = tail.substring(tail.indexOf(".")); + tail = type.substring(type.indexOf("#")+1); + if (tail.contains(".")) { + tail = tail.substring(tail.indexOf(".")); + } else { + tail = ""; + } } } for (StructureDefinition sdi : sdl) { - String path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "."; + String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; if (name.equals("**")) { - assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); + assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); for (ElementDefinition ed : sdi.getSnapshot().getElement()) { - if (ed.getPath().startsWith(path)) - for (TypeRefComponent t : ed.getType()) { - if (t.hasCode() && t.getCodeElement().hasValue()) { - String tn = null; - if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { - tn = sdi.getType() + "#" + ed.getPath(); - } else { - tn = t.getCode(); + if (ed.getPath().startsWith(path)) { + if (ed.hasContentReference()) { + String cpath = ed.getContentReference(); + String tn = sdi.getType()+cpath; + if (!result.hasType(worker, tn)) { + if (elementDependencies != null) { + elementDependencies.add(ed); } - if (t.getCode().equals("Resource")) { - for (String rn : worker.getResourceNames()) { - if (!result.hasType(worker, rn)) { - getChildTypesByName(result.addType(rn), "**", result, expr); - } + getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); + } + } else { + for (TypeRefComponent t : ed.getType()) { + if (t.hasCode() && t.getCodeElement().hasValue()) { + String tn = null; + if (Utilities.existsInList(t.getCode(), "Element", "BackboneElement", "Base") || cu.isAbstractType(t.getCode())) { + tn = sdi.getType()+"#"+ed.getPath(); + } else { + tn = t.getCode(); + } + if (t.getCode().equals("Resource")) { + for (String rn : worker.getResourceNames()) { + if (!result.hasType(worker, rn)) { + if (elementDependencies != null) { + elementDependencies.add(ed); + } + getChildTypesByName(result.addType(rn), "**", result, expr, null, elementDependencies); + } + } + } else if (!result.hasType(worker, tn)) { + if (elementDependencies != null) { + elementDependencies.add(ed); + } + getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); } - } else if (!result.hasType(worker, tn)) { - getChildTypesByName(result.addType(tn), "**", result, expr); } } } - } + } + } } else if (name.equals("*")) { - assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); + assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); for (ElementDefinition ed : sdi.getSnapshot().getElement()) { if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) for (TypeRefComponent t : ed.getType()) { if (Utilities.noString(t.getCode())) { // Element.id or Extension.url + if (elementDependencies != null) { + elementDependencies.add(ed); + } result.addType("System.string"); } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { - result.addType(sdi.getType() + "#" + ed.getPath()); + if (elementDependencies != null) { + elementDependencies.add(ed); + } + result.addType(sdi.getType()+"#"+ed.getPath()); } else if (t.getCode().equals("Resource")) { + if (elementDependencies != null) { + elementDependencies.add(ed); + } result.addTypes(worker.getResourceNames()); } else { + if (elementDependencies != null) { + elementDependencies.add(ed); + } result.addType(t.getCode()); + copyTargetProfiles(ed, t, focus, result); } } } } else { - path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "." + name; + path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; - ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); - if (ed != null) { - if (!Utilities.noString(ed.getFixedType())) + List edl = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); + for (ElementDefinitionMatch ed : edl) { + if (ed.getDefinition().isChoice()) { + result.setChoice(true); + } + if (!Utilities.noString(ed.getFixedType())) { + if (elementDependencies != null) { + elementDependencies.add(ed.definition); + } result.addType(ed.getFixedType()); - else { + } else if (ed.getSourceDefinition() != null) { + ProfiledType pt = new ProfiledType(sdi.getType()+"#"+ed.definition.getPath()); + result.addType(ed.getSourceDefinition().unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); + } else { for (TypeRefComponent t : ed.getDefinition().getType()) { if (Utilities.noString(t.getCode())) { - if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") - || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", - "Extension.url")) { - result.addType(TypeDetails.FP_NS, "string"); + if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { + if (elementDependencies != null) { + elementDependencies.add(ed.definition); + } + result.addType(TypeDetails.FP_NS, "System.String"); } - break; // throw new PathEngineException("Illegal reference to primitive value attribute - // @ "+path); + break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); } ProfiledType pt = null; - if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { - pt = new ProfiledType(sdi.getUrl() + "#" + path); + if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement") || isAbstractType(t.getCode())) { + pt = new ProfiledType(sdi.getUrl()+"#"+path); } else if (t.getCode().equals("Resource")) { + if (elementDependencies != null) { + elementDependencies.add(ed.definition); + } result.addTypes(worker.getResourceNames()); } else { - pt = new ProfiledType(t.getCode()); + pt = new ProfiledType(t.getWorkingCode()); } if (pt != null) { if (t.hasProfile()) { @@ -6216,7 +6377,11 @@ public class FHIRPathEngine { if (ed.getDefinition().hasBinding()) { pt.addBinding(ed.getDefinition().getBinding()); } - result.addType(pt); + if (elementDependencies != null) { + elementDependencies.add(ed.definition); + } + result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); + copyTargetProfiles(ed.getDefinition(), t, focus, result); } } } @@ -6226,17 +6391,27 @@ public class FHIRPathEngine { } } - private void addTypeAndDescendents(List sdl, StructureDefinition dt, - List types) { - sdl.add(dt); - for (StructureDefinition sd : types) { - if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) - && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { - addTypeAndDescendents(sdl, sd, types); + private void copyTargetProfiles(ElementDefinition ed, TypeRefComponent t, TypeDetails focus, TypeDetails result) { + if (t.hasTargetProfile()) { + for (CanonicalType u : t.getTargetProfile()) { + result.addTarget(u.primitiveValue()); + } + } else if (focus != null && focus.hasType("CodeableReference") && ed.getPath().endsWith(".reference") && focus.getTargets() != null) { // special case, targets are on parent + for (String s : focus.getTargets()) { + result.addTarget(s); } } } + private void addTypeAndDescendents(List sdl, StructureDefinition dt, List types) { + sdl.add(dt); + for (StructureDefinition sd : types) { + if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + addTypeAndDescendents(sdl, sd, types); + } + } + } + private void getClassInfoChildTypesByName(String name, TypeDetails result) { if (name.equals("namespace")) { result.addType(TypeDetails.FP_String); @@ -6246,6 +6421,7 @@ public class FHIRPathEngine { } } + private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { if (name.equals("namespace")) { result.addType(TypeDetails.FP_String); @@ -6255,58 +6431,92 @@ public class FHIRPathEngine { } } - private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, - ExpressionNode expr) throws PathEngineException { + + public List getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException { for (ElementDefinition ed : sd.getSnapshot().getElement()) { if (ed.getPath().equals(path)) { if (ed.hasContentReference()) { - return getElementDefinitionById(sd, ed.getContentReference()); + ElementDefinitionMatch res = getElementDefinitionById(sd, ed.getContentReference()); + if (res == null) { + throw new Error("Unable to find "+ed.getContentReference()); + } else { + res.sourceDefinition = ed; + } + return ml(res); } else { - return new ElementDefinitionMatch(ed, null); + return ml(new ElementDefinitionMatch(ed, null)); } } - if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) - && path.length() == ed.getPath().length() - 3) { - return new ElementDefinitionMatch(ed, null); + if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) { + return ml(new ElementDefinitionMatch(ed, null)); } - if (allowTypedName && ed.getPath().endsWith("[x]") - && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) - && path.length() > ed.getPath().length() - 3) { - String s = Utilities.uncapitalize(path.substring(ed.getPath().length() - 3)); + if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { + String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); if (primitiveTypes.contains(s)) { - return new ElementDefinitionMatch(ed, s); + return ml(new ElementDefinitionMatch(ed, s)); } else { - return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length() - 3)); + return ml(new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3))); } } - if (ed.getPath().contains(".") && path.startsWith(ed.getPath() + ".") && (ed.getType().size() > 0) - && !isAbstractType(ed.getType())) { + if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { // now we walk into the type. - if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this - throw new Error("Internal typing issue...."); + if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this, but we can get here with CDA + List list = new ArrayList<>(); + // for each type, does it have the next node in the path? + for (TypeRefComponent tr : ed.getType()) { + StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tr.getCode(), null), sd); + if (nsd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); + } + List edl = getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); + list.addAll(edl); + } + return list; } - StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, - ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null)); - if (nsd == null) { - throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), - "getElementDefinition"); + StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null), sd); + if (nsd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); } - return getElementDefinition(nsd, nsd.getId() + path.substring(ed.getPath().length()), allowTypedName, expr); + return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); } - if (ed.hasContentReference() && path.startsWith(ed.getPath() + ".")) { + if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); - return getElementDefinition(sd, m.definition.getPath() + path.substring(ed.getPath().length()), allowTypedName, - expr); + List res = getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr); + if (res.size() == 0) { + throw new Error("Unable to find "+ed.getContentReference()); + } else { + for (ElementDefinitionMatch item : res) { + item.sourceDefinition = ed; + } + } + return res; } } - return null; + return ml(null); + } + + private List ml(ElementDefinitionMatch item) { + List list = new ArrayList<>(); + if (item != null) { + list.add(item); + } + return list; } private boolean isAbstractType(List list) { - return list.size() != 1 ? true - : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); + if (list.size() != 1) { + return false; + } else { + return isAbstractType(list.get(0).getCode()); + } } + private boolean isAbstractType(String code) { + StructureDefinition sd = worker.fetchTypeDefinition(code); + return sd != null && sd.getAbstract() && sd.getKind() != StructureDefinitionKind.RESOURCE; + } + + private boolean hasType(ElementDefinition ed, String s) { for (TypeRefComponent t : ed.getType()) { if (s.equalsIgnoreCase(t.getCode())) { @@ -6317,23 +6527,27 @@ public class FHIRPathEngine { } private boolean hasDataType(ElementDefinition ed) { - return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") - || ed.getType().get(0).getCode().equals("BackboneElement")); + return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement") || isAbstractType(ed.getType().get(0).getCode())); } private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { + if (ref.startsWith(sd.getUrl()+"#")) { + ref = ref.replace(sd.getUrl()+"#", "#"); + } for (ElementDefinition ed : sd.getSnapshot().getElement()) { - if (ref.equals("#" + ed.getId())) { + if (ref.equals("#"+ed.getId())) { return new ElementDefinitionMatch(ed, null); } } return null; } + public boolean hasLog() { return log != null && log.length() > 0; } + public String takeLog() { if (!hasLog()) { return ""; @@ -6343,20 +6557,18 @@ public class FHIRPathEngine { return s; } - /** - * given an element definition in a profile, what element contains the - * differentiating fixed for the element, given the differentiating expresssion. - * The expression is only allowed to use a subset of FHIRPath + + /** given an element definition in a profile, what element contains the differentiating fixed + * for the element, given the differentiating expresssion. The expression is only allowed to + * use a subset of FHIRPath * * @param profile * @param element * @return - * @throws PathEngineException - * @throws DefinitionException + * @throws PathEngineException + * @throws DefinitionException */ - public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, - TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) - throws DefinitionException { + public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException { StructureDefinition sd = profile; TypedElementDefinition focus = null; boolean okToNotResolve = false; @@ -6365,15 +6577,14 @@ public class FHIRPathEngine { if (element.getElement().hasSlicing()) { ElementDefinition slice = pickMandatorySlice(sd, element.getElement()); if (slice == null) { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, - element.getElement().getId()); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId()); } element = new TypedElementDefinition(slice); } if (expr.getName().equals("$this")) { focus = element; - } else { + } else { List childDefinitions; childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); // if that's empty, get the children of the type @@ -6381,14 +6592,12 @@ public class FHIRPathEngine { sd = fetchStructureByType(element, expr); if (sd == null) { - throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, - element.getElement().getType().get(0).getProfile(), element.getElement().getId()); + throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId()); } childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); } for (ElementDefinition t : childDefinitions) { - if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an - // exetnsion with a fixed value (type slicing) + if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) focus = new TypedElementDefinition(t); break; } @@ -6400,22 +6609,17 @@ public class FHIRPathEngine { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId()); } if (element.getTypes().size() > 1) { - throw makeExceptionPlural(element.getTypes().size(), expr, - I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId()); + throw makeExceptionPlural(element.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId()); } if (!element.getTypes().get(0).hasTarget()) { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, - element.getElement().getId(), element.getElement().getType().get(0).getCode() + ")"); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")"); } if (element.getTypes().get(0).getTargetProfile().size() > 1) { - throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, - I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId()); + throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId()); } - sd = worker.fetchResource(StructureDefinition.class, - element.getTypes().get(0).getTargetProfile().get(0).getValue()); + sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue(), profile); if (sd == null) { - throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, - element.getTypes().get(0).getTargetProfile(), element.getElement().getId()); + throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId()); } focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep()); } else if ("extension".equals(expr.getName())) { @@ -6423,13 +6627,10 @@ public class FHIRPathEngine { List childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); for (ElementDefinition t : childDefinitions) { if (t.getPath().endsWith(".extension") && t.hasSliceName()) { - StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() - || t.getType().get(0).getProfile().isEmpty()) ? null - : worker.fetchResource(StructureDefinition.class, - t.getType().get(0).getProfile().get(0).getValue()); - while (exsd != null - && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { - exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition()); + StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? + null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue(), profile); + while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { + exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd); } if (exsd != null && exsd.getUrl().equals(targetUrl)) { if (profileUtilities.getChildMap(sd, t).isEmpty()) { @@ -6440,9 +6641,8 @@ public class FHIRPathEngine { } } } - if (focus == null) { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), - targetUrl, element.getElement().getId(), sd.getUrl()); + if (focus == null) { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl()); } } else if ("ofType".equals(expr.getName())) { if (!element.getElement().hasType()) { @@ -6455,11 +6655,11 @@ public class FHIRPathEngine { } atn.add(tr.getCode()); } - String stn = expr.getParameters().get(0).getName(); + String stn = expr.getParameters().get(0).getName(); okToNotResolve = true; if ((atn.contains(stn))) { if (element.getTypes().size() > 1) { - focus = new TypedElementDefinition(element.getSrc(), element.getElement(), stn); + focus = new TypedElementDefinition( element.getSrc(), element.getElement(), stn); } else { focus = element; } @@ -6473,22 +6673,18 @@ public class FHIRPathEngine { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); } - if (focus == null) { + if (focus == null) { if (okToNotResolve) { return null; } else { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), - element.getElement().getId(), profile.getUrl()); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl()); } } else { - // gdg 26-02-2022. If we're walking towards a resolve() and we're on a - // reference, and we try to walk into the reference - // then we don't do that. .resolve() is allowed on the Reference.reference, but - // the target of the reference will be defined + // gdg 26-02-2022. If we're walking towards a resolve() and we're on a reference, and we try to walk into the reference + // then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined // on the Reference, not the reference.reference. ExpressionNode next = expr.getInner(); - if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name - && next.getName().equals("reference")) { + if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) { next = next.getInner(); } if (next == null) { @@ -6499,8 +6695,7 @@ public class FHIRPathEngine { } } - private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) - throws DefinitionException { + private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException { List list = profileUtilities.getSliceList(sd, element); for (ElementDefinition ed : list) { if (ed.getMin() > 0) { @@ -6510,35 +6705,32 @@ public class FHIRPathEngine { return null; } - private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) - throws DefinitionException { + + private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException { if (ed.getTypes().size() == 0) { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId()); } if (ed.getTypes().size() > 1) { - throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, - ed.getElement().getId()); + throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId()); } if (ed.getTypes().get(0).getProfile().size() > 1) { - throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, - I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId()); + throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId()); } - if (ed.getTypes().get(0).hasProfile()) { - return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue()); + if (ed.getTypes().get(0).hasProfile()) { + return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue(), ed.getSrc()); } else { - return worker.fetchResource(StructureDefinition.class, - ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null)); + return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null), ed.getSrc()); } } + private boolean tailMatches(ElementDefinition t, String d) { String tail = tailDot(t.getPath()); if (d.contains("[")) { return tail.startsWith(d.substring(0, d.indexOf('['))); } else if (tail.equals(d)) { return true; - } else if (t.getType().size() == 1 && t.getType().get(0).getCode() != null && t.getPath() != null - && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) { + } else if (t.getType().size() == 1 && t.getType().get(0).getCode() != null && t.getPath() != null && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) { return tail.startsWith(d); } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) { return true; @@ -6556,7 +6748,7 @@ public class FHIRPathEngine { } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { return asBool(items.get(0), true); } else if (items.size() == 1) { - return Equality.True; + return Equality.True; } else { throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items)); } @@ -6566,12 +6758,9 @@ public class FHIRPathEngine { try { int i = Integer.parseInt(s); switch (i) { - case 0: - return Equality.False; - case 1: - return Equality.True; - default: - return Equality.Null; + case 0: return Equality.False; + case 1: return Equality.True; + default: return Equality.Null; } } catch (Exception e) { return Equality.Null; @@ -6581,9 +6770,9 @@ public class FHIRPathEngine { private Equality asBoolFromDec(String s) { try { BigDecimal d = new BigDecimal(s); - if (d.compareTo(BigDecimal.ZERO) == 0) { + if (d.compareTo(BigDecimal.ZERO) == 0) { return Equality.False; - } else if (d.compareTo(BigDecimal.ONE) == 0) { + } else if (d.compareTo(BigDecimal.ONE) == 0) { return Equality.True; } else { return Equality.Null; @@ -6594,20 +6783,19 @@ public class FHIRPathEngine { } private Equality asBool(Base item, boolean narrow) { - if (item instanceof BooleanType) { + if (item instanceof BooleanType) { return boolToTriState(((BooleanType) item).booleanValue()); } else if (item.isBooleanPrimitive()) { if (Utilities.existsInList(item.primitiveValue(), "true")) { return Equality.True; } else if (Utilities.existsInList(item.primitiveValue(), "false")) { return Equality.False; - } else { + } else { return Equality.Null; } } else if (narrow) { return Equality.False; - } else if (item instanceof IntegerType - || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) { + } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) { return asBoolFromInt(item.primitiveValue()); } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) { return asBoolFromDec(item.primitiveValue()); @@ -6623,7 +6811,7 @@ public class FHIRPathEngine { } else { return Equality.Null; } - } + } return Equality.Null; } @@ -6631,10 +6819,12 @@ public class FHIRPathEngine { return b ? Equality.True : Equality.False; } + public ValidationOptions getTerminologyServiceOptions() { return terminologyServiceOptions; } + public IWorkerContext getWorker() { return worker; } @@ -6655,4 +6845,23 @@ public class FHIRPathEngine { this.liquidMode = liquidMode; } + public ProfileUtilities getProfileUtilities() { + return profileUtilities; + } + + public boolean isAllowDoubleQuotes() { + return allowDoubleQuotes; + } + public void setAllowDoubleQuotes(boolean allowDoubleQuotes) { + this.allowDoubleQuotes = allowDoubleQuotes; + } + + public boolean isEmitSQLonFHIRWarning() { + return emitSQLonFHIRWarning; + } + + public void setEmitSQLonFHIRWarning(boolean emitSQLonFHIRWarning) { + this.emitSQLonFHIRWarning = emitSQLonFHIRWarning; + } + } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/TypeDetails.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/TypeDetails.java index 4d879beb6..cacde0457 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/TypeDetails.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/fhirpath/TypeDetails.java @@ -32,6 +32,8 @@ package org.hl7.fhir.r4.fhirpath; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,32 +44,49 @@ import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; + public class TypeDetails { + public class ProfiledTypeSorter implements Comparator { + + @Override + public int compare(ProfiledType o1, ProfiledType o2) { + return o1.uri.compareTo(o2.uri); + } + + } + public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/"; public static final String FP_NS = "http://hl7.org/fhirpath/"; - public static final String FP_String = "http://hl7.org/fhirpath/String"; - public static final String FP_Boolean = "http://hl7.org/fhirpath/Boolean"; - public static final String FP_Integer = "http://hl7.org/fhirpath/Integer"; - public static final String FP_Decimal = "http://hl7.org/fhirpath/Decimal"; - public static final String FP_Quantity = "http://hl7.org/fhirpath/Quantity"; - public static final String FP_DateTime = "http://hl7.org/fhirpath/DateTime"; - public static final String FP_Time = "http://hl7.org/fhirpath/Time"; - public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/SimpleTypeInfo"; - public static final String FP_ClassInfo = "http://hl7.org/fhirpath/ClassInfo"; + public static final String FP_String = "http://hl7.org/fhirpath/System.String"; + public static final String FP_Boolean = "http://hl7.org/fhirpath/System.Boolean"; + public static final String FP_Integer = "http://hl7.org/fhirpath/System.Integer"; + public static final String FP_Decimal = "http://hl7.org/fhirpath/System.Decimal"; + public static final String FP_Quantity = "http://hl7.org/fhirpath/System.Quantity"; + public static final String FP_DateTime = "http://hl7.org/fhirpath/System.DateTime"; + public static final String FP_Time = "http://hl7.org/fhirpath/System.Time"; + public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/System.SimpleTypeInfo"; + public static final String FP_ClassInfo = "http://hl7.org/fhirpath/System.ClassInfo"; public static final Set FP_NUMBERS = new HashSet(Arrays.asList(FP_Integer, FP_Decimal)); public static class ProfiledType { + @Override + public String toString() { + return uri; + } + private String uri; private List profiles; // or, not and private List bindings; - + public ProfiledType(String n) { - uri = ns(n); + uri = ns(n); } - + public String getUri() { return uri; } @@ -75,7 +94,6 @@ public class TypeDetails { public boolean hasProfiles() { return profiles != null && profiles.size() > 0; } - public List getProfiles() { return profiles; } @@ -83,13 +101,12 @@ public class TypeDetails { public boolean hasBindings() { return bindings != null && bindings.size() > 0; } - public List getBindings() { return bindings; } public static String ns(String n) { - return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS + n; + return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS+n; } public void addProfile(String profile) { @@ -113,14 +130,26 @@ public class TypeDetails { for (UriType u : list) profiles.add(u.getValue()); } - public boolean isSystemType() { return uri.startsWith(FP_NS); } - } + public String describeMin() { + if (uri.startsWith(FP_NS)) { + return "System."+uri.substring(FP_NS.length()); + } + if (uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { + return "FHIR."+uri.substring("http://hl7.org/fhir/StructureDefinition/".length()); + } + return uri; + } + + } + private List types = new ArrayList(); private CollectionStatus collectionStatus; + private Set targets; // or, not and, canonical urls + private boolean choice; public TypeDetails(CollectionStatus collectionStatus, String... names) { super(); @@ -129,7 +158,6 @@ public class TypeDetails { this.types.add(new ProfiledType(n)); } } - public TypeDetails(CollectionStatus collectionStatus, Set names) { super(); this.collectionStatus = collectionStatus; @@ -137,20 +165,21 @@ public class TypeDetails { addType(new ProfiledType(n)); } } - public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) { super(); this.collectionStatus = collectionStatus; this.types.add(pt); } - + + private TypeDetails() { + } + public String addType(String n) { ProfiledType pt = new ProfiledType(n); String res = pt.uri; addType(pt); return res; } - public String addType(String n, String p) { ProfiledType pt = new ProfiledType(n); pt.addProfile(p); @@ -158,7 +187,7 @@ public class TypeDetails { addType(pt); return res; } - + public void addType(ProfiledType pt) { for (ProfiledType et : types) { if (et.uri.equals(pt.uri)) { @@ -181,99 +210,163 @@ public class TypeDetails { return; } } - types.add(pt); + types.add(pt); + } + + public void addType(CollectionStatus status, ProfiledType pt) { + addType(pt); + if (collectionStatus == null) { + collectionStatus = status; + } else { + switch (status) { + case ORDERED: + if (collectionStatus == CollectionStatus.SINGLETON) { + collectionStatus = status; + } + break; + case SINGLETON: + break; + case UNORDERED: + collectionStatus = status; + break; + default: + break; + } + } } public void addTypes(Collection names) { - for (String n : names) + for (String n : names) addType(new ProfiledType(n)); } - + public boolean hasType(IWorkerContext context, String... tn) { - for (String n : tn) { + for (String n: tn) { String t = ProfiledType.ns(n); if (typesContains(t)) return true; - if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", - "ClassInfo", "SimpleTypeInfo")) { - t = FP_NS + Utilities.capitalize(n); - if (typesContains(t)) + if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { + t = FP_NS+"System."+Utilities.capitalize(n); + if (typesContains(t)) { return true; + } + } + t = ProfiledType.ns(n); + StructureDefinition sd = context.fetchTypeDefinition(t); + if (sd != null && sd.getKind() != StructureDefinitionKind.LOGICAL && Utilities.existsInList(sd.getType(), "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time")) { + t = FP_NS+"System."+Utilities.capitalize(sd.getType()); + if (typesContains(t)) { + return true; + } } } - for (String n : tn) { + for (String n: tn) { String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n; String tail = null; if (n.contains("#")) { - tail = n.substring(n.indexOf("#") + 1); + tail = n.substring( n.indexOf("#")+1); tail = tail.substring(tail.indexOf(".")); } - String t = ProfiledType.ns(n); - StructureDefinition sd = context.fetchResource(StructureDefinition.class, t); - while (sd != null) { - if (tail == null && typesContains(sd.getUrl())) - return true; - if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl()))) - return true; - if (tail != null && typesContains(sd.getUrl() + "#" + sd.getType() + tail)) - return true; - if (sd.hasBaseDefinition()) { - if (sd.getType().equals("uri")) - sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); - else - sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); - } else - sd = null; + List list = new ArrayList<>(); + if (!Utilities.isAbsoluteUrl(n)) { + list.addAll(context.fetchTypeDefinitions(n)); + } else { + String t = ProfiledType.ns(n); + StructureDefinition sd = context.fetchResource(StructureDefinition.class, t); + if (sd != null) { + list.add(sd); + } + } + for (int i = 0; i < list.size(); i++) { + StructureDefinition sd = list.get(i); + while (sd != null) { + if (tail == null && typesContains(sd.getUrl())) + return true; + if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl()))) + return true; + if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail)) + return true; + if ("http://hl7.org/fhir/StructureDefinition/string".equals(sd.getUrl()) && typesContains(FP_String)) { + return true; // this is work around for R3 + } + if (sd.hasBaseDefinition()) { + if (sd.getType().equals("uri")) + sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); + else + sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } else { + sd = null; + } + } } } return false; } - + private String getSystemType(String url) { if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { String code = url.substring(40); - if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity")) - return FP_NS + Utilities.capitalize(code); + if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity")) + return FP_NS+"System.."+Utilities.capitalize(code); } return null; } - + private boolean typesContains(String t) { for (ProfiledType pt : types) if (pt.uri.equals(t)) return true; return false; } - + public void update(TypeDetails source) { for (ProfiledType pt : source.types) addType(pt); - if (collectionStatus == null) + if (collectionStatus == null || collectionStatus == CollectionStatus.SINGLETON) collectionStatus = source.collectionStatus; else if (source.collectionStatus == CollectionStatus.UNORDERED) collectionStatus = source.collectionStatus; else collectionStatus = CollectionStatus.ORDERED; + if (source.targets != null) { + if (targets == null) { + targets = new HashSet<>(); + } + targets.addAll(source.targets); + } + if (source.isChoice()) { + choice = true; + } } - + public TypeDetails union(TypeDetails right) { TypeDetails result = new TypeDetails(null); if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) result.collectionStatus = CollectionStatus.UNORDERED; - else + else result.collectionStatus = CollectionStatus.ORDERED; for (ProfiledType pt : types) result.addType(pt); for (ProfiledType pt : right.types) result.addType(pt); + if (targets != null || right.targets != null) { + result.targets = new HashSet<>(); + if (targets != null) { + result.targets.addAll(targets); + } + if (right.targets != null) { + result.targets.addAll(right.targets); + } + } + return result; } - + public TypeDetails intersect(TypeDetails right) { TypeDetails result = new TypeDetails(null); if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) result.collectionStatus = CollectionStatus.UNORDERED; - else + else result.collectionStatus = CollectionStatus.ORDERED; for (ProfiledType pt : types) { boolean found = false; @@ -284,77 +377,106 @@ public class TypeDetails { } for (ProfiledType pt : right.types) result.addType(pt); + if (targets != null && right.targets != null) { + result.targets = new HashSet<>(); + for (String s : targets) { + if (right.targets.contains(s)) { + result.targets.add(s); + } + } + } + return result; } - + public boolean hasNoTypes() { return types.isEmpty(); } - public Set getTypes() { Set res = new HashSet(); for (ProfiledType pt : types) res.add(pt.uri); return res; } - public TypeDetails toSingleton() { TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); result.types.addAll(types); return result; } - + public TypeDetails toOrdered() { + TypeDetails result = new TypeDetails(CollectionStatus.ORDERED); + result.types.addAll(types); + return result; + } + public TypeDetails toUnordered() { + TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); + result.types.addAll(types); + return result; + } public CollectionStatus getCollectionStatus() { return collectionStatus; } - + + private boolean hasType(ProfiledType pt) { + return hasType(pt.uri); + } + public boolean hasType(String n) { String t = ProfiledType.ns(n); if (typesContains(t)) return true; - if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", - "ClassInfo", "SimpleTypeInfo")) { - t = FP_NS + Utilities.capitalize(n); + if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { + t = FP_NS+"System."+Utilities.capitalize(n); if (typesContains(t)) return true; } return false; } - + public boolean hasType(Set tn) { - for (String n : tn) { + for (String n: tn) { String t = ProfiledType.ns(n); if (typesContains(t)) return true; - if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", - "ClassInfo", "SimpleTypeInfo")) { - t = FP_NS + Utilities.capitalize(n); + if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { + t = FP_NS+"System."+Utilities.capitalize(n); if (typesContains(t)) return true; } } return false; } - + public String describe() { - return getTypes().toString(); + return Utilities.sorted(getTypes()).toString(); } - + + public String describeMin() { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (ProfiledType pt : sortedTypes(types)) + b.append(pt.describeMin()); + return b.toString(); + } + + private List sortedTypes(List types2) { + List list = new ArrayList<>(); + Collections.sort(list, new ProfiledTypeSorter()); + return list; + } + public String getType() { for (ProfiledType pt : types) return pt.uri; return null; } - + @Override public String toString() { - return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) - + getTypes().toString(); + return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) + getTypes().toString(); } - public String getTypeCode() throws DefinitionException { if (types.size() != 1) - throw new DefinitionException("Multiple types? (" + types.toString() + ")"); + throw new DefinitionException("Multiple types? ("+types.toString()+")"); for (ProfiledType pt : types) if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) return pt.uri.substring(40); @@ -362,11 +484,9 @@ public class TypeDetails { return pt.uri; return null; } - public List getProfiledTypes() { return types; } - public boolean hasBinding() { for (ProfiledType pt : types) { if (pt.hasBindings()) @@ -374,7 +494,6 @@ public class TypeDetails { } return false; } - public ElementDefinitionBindingComponent getBinding() { for (ProfiledType pt : types) { for (ElementDefinitionBindingComponent b : pt.getBindings()) @@ -382,5 +501,97 @@ public class TypeDetails { } return null; } + + public void addTarget(String url) { + if (targets == null) { + targets = new HashSet<>(); + } + targets.add(url); + } + public Set getTargets() { + return targets; + } + public boolean typesHaveTargets() { + for (ProfiledType pt : types) { + if (Utilities.existsInList(pt.getUri(), "Reference", "CodeableReference", "canonical", "http://hl7.org/fhir/StructureDefinition/Reference", "http://hl7.org/fhir/StructureDefinition/CodeableReference", "http://hl7.org/fhir/StructureDefinition/canonical")) { + return true; + } + } + return false; + } + public void addTargets(Set src) { + if (src != null) { + for (String s : src) { + addTarget(s); + } + } + + } + public TypeDetails copy() { + TypeDetails td = new TypeDetails(); + td.types.addAll(types); + td.collectionStatus = collectionStatus; + if (targets != null ) { + td.targets = new HashSet<>(); + td.targets.addAll(targets); + } + return td; + } + + public boolean matches(TypeDetails other) { + boolean result = collectionStatus == other.collectionStatus && types.equals(other.types); + if (targets == null) { + return result && other.targets == null; + } else { + return result && targets.equals(other.targets); + } + + } + public void addTypes(TypeDetails other) { + if (other.collectionStatus != CollectionStatus.SINGLETON) { + if (other.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) { + collectionStatus = CollectionStatus.UNORDERED; + } else { + collectionStatus = CollectionStatus.ORDERED; + } + } + for (ProfiledType pt : other.types) { + addType(pt); + } + if (other.targets != null) { + if (targets == null) { + targets = new HashSet<>(); + } + targets.addAll(other.targets); + } + } + + public boolean contains(TypeDetails other) { + // TODO Auto-generated method stub + if (other.collectionStatus != collectionStatus) { + return false; + } + for (ProfiledType pt : other.types) { + if (!hasType(pt)) { + return false; + } + } + return true; + } + public static TypeDetails empty() { + return new TypeDetails(CollectionStatus.SINGLETON); + } + public boolean isList() { + return collectionStatus != null && collectionStatus.isList(); + } + + // for SQL-on-FHIR: warnings when .ofType() is not paired with a choice element + public void setChoice(boolean b) { + choice = true; + } + public boolean isChoice() { + return choice; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base.java index 005bd9b03..3c3f9d319 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base.java @@ -212,6 +212,17 @@ public abstract class Base implements Serializable, IBase, IElement { return result; } + public Base getChildValueByName(String name) { + Property p = getChildByName(name); + if (p != null && p.hasValues()) { + if (p.getValues().size() > 1) { + throw new Error("Too manye values for "+name+" found"); + } else { + return p.getValues().get(0); + } + } + return null; + } public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException { if (name.equals("*")) { List children = new ArrayList(); diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Constants.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Constants.java index 99151c6db..d9d87eaa6 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Constants.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Constants.java @@ -39,5 +39,7 @@ public class Constants { public final static String URI_REGEX = "((http|https)://([A-Za-z0-9\\\\\\.\\:\\%\\$]*\\/)*)?(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?"; public final static String LOCAL_REF_REGEX = "(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}"; public final static String NS_SYSTEM_TYPE = "http://hl7.org/fhirpath/System."; + public static final String NS_FHIR_ROOT = "http://hl7.org/fhir"; + public static final String NS_CDA_ROOT = "http://hl7.org/cda/stds/core"; } \ No newline at end of file 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 6d61d654a..83f355892 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 @@ -4839,4 +4839,17 @@ public class StructureDefinition extends MetadataResource { return hasVersion() ? getUrl()+"|"+getVersion() : getUrl(); } + + public String getTypeName() { + String t = getType(); + return StructureDefinitionKind.LOGICAL.equals(getKind()) && t.contains("/") ? t.substring(t.lastIndexOf("/")+1) : t; + } + + public String getTypeTail() { + if (getType().contains("/")) { + return getType().substring(getType().lastIndexOf("/")+1); + } else { + return getType(); + } + } } 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 index 5292000cd..5b22e227b 100644 --- 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 @@ -139,7 +139,7 @@ public class PECodeGenerator { w(b, "public class "+name+" extends PEGeneratedBase {"); w(b); if (url != null) { - w(b, " private static final String CANONICAL_URL = \""+url+"\";"); + w(b, " public static final String CANONICAL_URL = \""+url+"\";"); w(b); } if (enums.length() > 0) { diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/LiquidEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/LiquidEngine.java index 088647551..84b926902 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/LiquidEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/LiquidEngine.java @@ -444,4 +444,8 @@ public class LiquidEngine implements IEvaluationContext { return engine.getWorker().fetchResource(ValueSet.class, url); } + @Override + public boolean paramIsType(String name, int index) { + return false; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java index 2cda3a053..ec273af51 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java @@ -252,6 +252,10 @@ public class StructureMapUtilities { throw new Error("Not Implemented Yet"); } + @Override + public boolean paramIsType(String name, int index) { + return false; + } } private IWorkerContext worker; @@ -751,7 +755,7 @@ public class StructureMapUtilities { } public StructureMap parse(String text, String srcName) throws FHIRException { - FHIRLexer lexer = new FHIRLexer(text, srcName); + FHIRLexer lexer = new FHIRLexer(text, srcName, true, true); if (lexer.done()) throw lexer.error("Map Input cannot be empty"); lexer.skipComments(); diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Cell.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Cell.java new file mode 100644 index 000000000..331be75b2 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Cell.java @@ -0,0 +1,43 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.r4.utils.sql.Cell; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.Value; + + +public class Cell { + private Column column; + private List values = new ArrayList<>(); + + public Cell(Column column) { + super(); + this.column = column; + } + + public Cell(Column column, Value value) { + super(); + this.column = column; + this.values.add(value); + } + + public Column getColumn() { + return column; + } + + public List getValues() { + return values; + } + + public Cell copy() { + Cell cell = new Cell(column); + for (Value v : values) { + cell.values.add(v); // values are immutable, so we don't need to clone them + } + return cell; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Column.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Column.java new file mode 100644 index 000000000..5107957b0 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Column.java @@ -0,0 +1,102 @@ +package org.hl7.fhir.r4.utils.sql; + +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.ColumnKind; + +public class Column { + + private String name; + private int length; + private String type; + private ColumnKind kind; + private boolean isColl; + private boolean duplicateReported; + + protected Column() { + super(); + } + + protected Column(String name, boolean isColl, String type, ColumnKind kind) { + super(); + this.name = name; + this.isColl = isColl; + this.type = type; + this.kind = kind; + } + + public String getName() { + return name; + } + public int getLength() { + return length; + } + public ColumnKind getKind() { + return kind; + } + + public void setName(String name) { + this.name = name; + } + + public void setLength(int length) { + this.length = length; + } + + public void setKind(ColumnKind kind) { + this.kind = kind; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isColl() { + return isColl; + } + + public void setColl(boolean isColl) { + this.isColl = isColl; + } + + public String diff(Column other) { + if (!name.equals(other.name)) { + return "Names differ: '"+name+"' vs '"+other.name+"'"; + } + if (kind != ColumnKind.Null && other.kind != ColumnKind.Null) { + if (length != other.length) { + return "Lengths differ: '"+length+"' vs '"+other.length+"'"; + } + if (kind != other.kind) { + return "Kinds differ: '"+kind+"' vs '"+other.kind+"'"; + } + if (isColl != other.isColl) { + return "Collection status differs: '"+isColl+"' vs '"+other.isColl+"'"; + } + } else if (kind == ColumnKind.Null) { + kind = other.kind; + length = other.length; + isColl = other.isColl; + } + return null; + } + + public boolean isDuplicateReported() { + return duplicateReported; + } + + public void setDuplicateReported(boolean duplicateReported) { + this.duplicateReported = duplicateReported; + } + + @Override + public String toString() { + return "Column [name=" + name + ", length=" + length + ", type=" + type + ", kind=" + kind + ", isColl=" + isColl + + "]"; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/ColumnKind.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/ColumnKind.java new file mode 100644 index 000000000..d3d513446 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/ColumnKind.java @@ -0,0 +1,5 @@ +package org.hl7.fhir.r4.utils.sql; + +public enum ColumnKind { + String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex, Null +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Provider.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Provider.java new file mode 100644 index 000000000..61df9f70c --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Provider.java @@ -0,0 +1,11 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.util.List; + +import org.hl7.fhir.r4.model.Base; + +public interface Provider { + List fetch(String resourceType); + + Base resolveReference(Base rootResource, String ref, String specifiedResourceType); +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Runner.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Runner.java new file mode 100644 index 000000000..0990aee22 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Runner.java @@ -0,0 +1,574 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.fhirpath.ExpressionNode; +import org.hl7.fhir.r4.fhirpath.FHIRPathEngine; +import org.hl7.fhir.r4.fhirpath.TypeDetails; +import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r4.fhirpath.FHIRPathEngine.IEvaluationContext; +import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FunctionDetails; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.Base64BinaryType; +import org.hl7.fhir.r4.model.BaseDateTimeType; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Property; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.utils.sql.Cell; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.Provider; +import org.hl7.fhir.r4.utils.sql.Storage; +import org.hl7.fhir.r4.utils.sql.Store; +import org.hl7.fhir.r4.utils.sql.Validator; +import org.hl7.fhir.r4.utils.sql.Value; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.validation.ValidationMessage; + + +/** + * How to use the Runner: + * + * create a resource, and fill out: + * the context (supports the FHIRPathEngine) + * a store that handles the output + * a tracker - if you want + * + * Once it's created, you either run it as a batch, or in trickle mode + * + * (1) Batch Mode + * + * * provide a provider + * * call execute() with a ViewDefinition + * * wait... (watch with an observer if you want to track progress) + * + * (2) Trickle Mode + * * call 'prepare', and keep the WorkContext that's returned + * * each time there's a resource to process, call processResource and pass in the workContext and the resource + * * when done, call finish(WorkContext) + */ + +public class Runner implements IEvaluationContext { + + public interface IRunnerObserver { + public void handleRow(Base resource, int total, int cursor); + } + + public class WorkContext { + private JsonObject vd; + private Store store; + protected WorkContext(JsonObject vd) { + super(); + this.vd = vd; + } + + } + private IWorkerContext context; + private Provider provider; + private Storage storage; + private IRunnerObserver observer; + private List prohibitedNames = new ArrayList(); + private FHIRPathEngine fpe; + + private String resourceName; + private List issues; + private int resCount; + + + public IWorkerContext getContext() { + return context; + } + public void setContext(IWorkerContext context) { + this.context = context; + } + + public Provider getProvider() { + return provider; + } + public void setProvider(Provider provider) { + this.provider = provider; + } + + public Storage getStorage() { + return storage; + } + public void setStorage(Storage storage) { + this.storage = storage; + } + + public List getProhibitedNames() { + return prohibitedNames; + } + + public void execute(JsonObject viewDefinition) { + execute("$", viewDefinition); + } + + public void execute(String path, JsonObject viewDefinition) { + WorkContext wc = prepare(path, viewDefinition); + try { + evaluate(wc); + } finally { + finish(wc); + } + } + + private void evaluate(WorkContext wc) { + List data = provider.fetch(resourceName); + + int i = 0; + for (Base b : data) { + if (observer != null) { + observer.handleRow(b, data.size(), i); + } + processResource(wc.vd, wc.store, b); + i++; + } + } + + public WorkContext prepare(String path, JsonObject viewDefinition) { + WorkContext wc = new WorkContext(viewDefinition); + if (context == null) { + throw new FHIRException("No context provided"); + } + fpe = new FHIRPathEngine(context); + fpe.setHostServices(this); + fpe.setEmitSQLonFHIRWarning(true); + if (viewDefinition == null) { + throw new FHIRException("No viewDefinition provided"); + } + if (provider == null) { + throw new FHIRException("No provider provided"); + } + if (storage == null) { + throw new FHIRException("No storage provided"); + } + Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName()); + validator.checkViewDefinition(path, viewDefinition); + issues = validator.getIssues(); + validator.dump(); + validator.check(); + resourceName = validator.getResourceName(); + wc.store = storage.createStore(wc.vd.asString("name"), (List) wc.vd.getUserData("columns")); + return wc; + } + + public void processResource(WorkContext wc, Base b) { + if (observer != null) { + observer.handleRow(b, -1, resCount); + } + processResource(wc.vd, wc.store, b); + resCount++; + wc.store.flush(); + } + + private void processResource(JsonObject vd, Store store, Base b) { + boolean ok = true; + for (JsonObject w : vd.getJsonObjects("where")) { + String expr = w.asString("path"); + ExpressionNode node = fpe.parse(expr); + boolean pass = fpe.evaluateToBoolean(vd, b, b, b, node); + if (!pass) { + ok = false; + break; + } + } + if (ok) { + List> rows = new ArrayList<>(); + rows.add(new ArrayList()); + + for (JsonObject select : vd.getJsonObjects("select")) { + executeSelect(vd, select, b, rows); + } + for (List row : rows) { + storage.addRow(store, row); + } + } + } + + public void finish(WorkContext wc) { + storage.finish(wc.store); + } + + private void executeSelect(JsonObject vd, JsonObject select, Base b, List> rows) { + List focus = new ArrayList<>(); + + if (select.has("forEach")) { + focus.addAll(executeForEach(vd, select, b)); + } else if (select.has("forEachOrNull")) { + + focus.addAll(executeForEachOrNull(vd, select, b)); + if (focus.isEmpty()) { + List columns = (List) select.getUserData("columns"); + for (List row : rows) { + for (Column c : columns) { + Cell cell = cell(row, c.getName()); + if (cell == null) { + row.add(new Cell(c, null)); + } + } + } + return; + } + } else { + focus.add(b); + } + + // } else if (select.has("unionAll")) { + // focus.addAll(executeUnion(select, b)); + + List> tempRows = new ArrayList<>(); + tempRows.addAll(rows); + rows.clear(); + + for (Base f : focus) { + List> rowsToAdd = cloneRows(tempRows); + + for (JsonObject column : select.getJsonObjects("column")) { + executeColumn(vd, column, f, rowsToAdd); + } + + for (JsonObject sub : select.getJsonObjects("select")) { + executeSelect(vd, sub, f, rowsToAdd); + } + + executeUnionAll(vd, select.getJsonObjects("unionAll"), f, rowsToAdd); + + rows.addAll(rowsToAdd); + } + } + + private void executeUnionAll(JsonObject vd, List unionList, Base b, List> rows) { + if (unionList.isEmpty()) { + return; + } + List> sourceRows = new ArrayList<>(); + sourceRows.addAll(rows); + rows.clear(); + + for (JsonObject union : unionList) { + List> tempRows = new ArrayList<>(); + tempRows.addAll(sourceRows); + executeSelect(vd, union, b, tempRows); + rows.addAll(tempRows); + } + } + + private List> cloneRows(List> rows) { + List> list = new ArrayList<>(); + for (List row : rows) { + list.add(cloneRow(row)); + } + return list; + } + + private List cloneRow(List cells) { + List list = new ArrayList<>(); + for (Cell cell : cells) { + list.add(cell.copy()); + } + return list; + } + + private List executeForEach(JsonObject vd, JsonObject focus, Base b) { + ExpressionNode n = (ExpressionNode) focus.getUserData("forEach"); + List result = new ArrayList<>(); + result.addAll(fpe.evaluate(vd, b, n)); + return result; + } + + private List executeForEachOrNull(JsonObject vd, JsonObject focus, Base b) { + ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull"); + List result = new ArrayList<>(); + result.addAll(fpe.evaluate(vd, b, n)); + return result; + } + + private void executeColumn(JsonObject vd, JsonObject column, Base b, List> rows) { + ExpressionNode n = (ExpressionNode) column.getUserData("path"); + List bl2 = new ArrayList<>(); + if (b != null) { + bl2.addAll(fpe.evaluate(vd, b, n)); + } + Column col = (Column) column.getUserData("column"); + if (col == null) { + System.out.println("Error"); + } else { + for (List row : rows) { + Cell c = cell(row, col.getName()); + if (c == null) { + c = new Cell(col); + row.add(c); + } + if (!bl2.isEmpty()) { + if (bl2.size() + c.getValues().size() > 1) { + // this is a problem if collection != true or if the storage can't deal with it + // though this should've been picked up before now - but there are circumstances where it wouldn't be + if (!c.getColumn().isColl()) { + throw new FHIRException("The column "+c.getColumn().getName()+" is not allowed multiple values, but at least one row has multiple values"); + } + } + for (Base b2 : bl2) { + c.getValues().add(genValue(c.getColumn(), b2)); + } + } + } + } + } + + + private Value genValue(Column column, Base b) { + if (column.getKind() == null) { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type (null) for column "+column.getName()); // can't happen + } + switch (column.getKind()) { + case Binary: + if (b instanceof Base64BinaryType) { + Base64BinaryType bb = (Base64BinaryType) b; + return Value.makeBinary(bb.primitiveValue(), bb.getValue()); + } else if (b.isBooleanPrimitive()) { // ElementModel + return Value.makeBinary(b.primitiveValue(), Base64.decodeBase64(b.primitiveValue())); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a binary column for column "+column.getName()); + } + case Boolean: + if (b instanceof BooleanType) { + BooleanType bb = (BooleanType) b; + return Value.makeBoolean(bb.primitiveValue(), bb.booleanValue()); + } else if (b.isBooleanPrimitive()) { // ElementModel + return Value.makeBoolean(b.primitiveValue(), "true".equals(b.primitiveValue())); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a boolean column for column "+column.getName()); + } + case Complex: + if (b.isPrimitive()) { + throw new FHIRException("Attempt to add a primitive type "+b.fhirType()+" to a complex column for column "+column.getName()); + } else { + return Value.makeComplex(b); + } + case DateTime: + if (b instanceof BaseDateTimeType) { + BaseDateTimeType d = (BaseDateTimeType) b; + return Value.makeDate(d.primitiveValue(), d.getValue()); + } else if (b.isPrimitive() && b.isDateTime()) { // ElementModel + return Value.makeDate(b.primitiveValue(), b.dateTimeValue().getValue()); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); + } + case Decimal: + if (b instanceof DecimalType) { + DecimalType d = (DecimalType) b; + return Value.makeDecimal(d.primitiveValue(), d.getValue()); + } else if (b.isPrimitive()) { // ElementModel + return Value.makeDecimal(b.primitiveValue(), new BigDecimal(b.primitiveValue())); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); + } + case Integer: + if (b instanceof IntegerType) { + IntegerType i = (IntegerType) b; + return Value.makeInteger(i.primitiveValue(), i.getValue()); + } else if (b.isPrimitive()) { // ElementModel + return Value.makeInteger(b.primitiveValue(), Integer.valueOf(b.primitiveValue())); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); + } + case String: + if (b.isPrimitive()) { + return Value.makeString(b.primitiveValue()); + } else { + throw new FHIRException("Attempt to add a complex type "+b.fhirType()+" to a string column for column "+column.getName()); + } + case Time: + if (b.fhirType().equals("time")) { + return Value.makeString(b.primitiveValue()); + } else { + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a time column for column "+column.getName()); + } + default: + throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type for column "+column.getName()); + } + } + + private Column column(String columnName, List columns) { + for (Column t : columns) { + if (t.getName().equalsIgnoreCase(columnName)) { + return t; + } + } + return null; + } + + private Cell cell(List cells, String columnName) { + for (Cell t : cells) { + if (t.getColumn().getName().equalsIgnoreCase(columnName)) { + return t; + } + } + return null; + } + + @Override + public List resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException { + List list = new ArrayList(); + if (explicitConstant) { + JsonObject vd = (JsonObject) appContext; + JsonObject constant = findConstant(vd, name); + if (constant != null) { + Base b = (Base) constant.getUserData("value"); + if (b != null) { + list.add(b); + } + } + } + return list; + } + + @Override + public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException { + if (explicitConstant) { + JsonObject vd = (JsonObject) appContext; + JsonObject constant = findConstant(vd, name.substring(1)); + if (constant != null) { + Base b = (Base) constant.getUserData("value"); + if (b != null) { + return new TypeDetails(CollectionStatus.SINGLETON, b.fhirType()); + } + } + } + return null; + } + + private JsonObject findConstant(JsonObject vd, String name) { + for (JsonObject o : vd.getJsonObjects("constant")) { + if (name.equals(o.asString("name"))) { + return o; + } + } + return null; + } + @Override + public boolean log(String argument, List focus) { + throw new Error("Not implemented yet: log"); + } + + @Override + public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) { + switch (functionName) { + case "getResourceKey" : return new FunctionDetails("Unique Key for resource", 0, 0); + case "getReferenceKey" : return new FunctionDetails("Unique Key for resource that is the target of the reference", 0, 1); + default: return null; + } + } + @Override + public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List parameters) throws PathEngineException { + switch (functionName) { + case "getResourceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string"); + case "getReferenceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string"); + default: throw new Error("Not known: "+functionName); + } + } + + @Override + public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) { + switch (functionName) { + case "getResourceKey" : return executeResourceKey(focus); + case "getReferenceKey" : return executeReferenceKey(null, focus, parameters); + default: throw new Error("Not known: "+functionName); + } + } + + private List executeResourceKey(List focus) { + List base = new ArrayList(); + if (focus.size() == 1) { + Base res = focus.get(0); + if (!res.hasUserData("Storage.key")) { + String key = storage.getKeyForSourceResource(res); + if (key == null) { + throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase()); + } else { + res.setUserData("Storage.key", key); + } + } + base.add(new StringType(res.getUserString("Storage.key"))); + } + return base; + } + + private List executeReferenceKey(Base rootResource, List focus, List> parameters) { + String rt = null; + if (parameters.size() > 0) { + rt = parameters.get(0).get(0).primitiveValue(); + if (rt.startsWith("FHIR.")) { + rt = rt.substring(5); + } + } + List base = new ArrayList(); + if (focus.size() == 1) { + Base res = focus.get(0); + String ref = null; + if (res.fhirType().equals("Reference")) { + ref = getRef(res); + } else if (res.isPrimitive()) { + ref = res.primitiveValue(); + } else { + throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType()); + } + if (ref != null) { + Base target = provider.resolveReference(rootResource, ref, rt); + if (target != null) { + if (!res.hasUserData("Storage.key")) { + String key = storage.getKeyForTargetResource(target); + if (key == null) { + throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase()); + } else { + res.setUserData("Storage.key", key); + } + } + base.add(new StringType(res.getUserString("Storage.key"))); + } + } + } + return base; + } + + private String getRef(Base res) { + Property prop = res.getChildByName("reference"); + if (prop != null && prop.getValues().size() == 1) { + return prop.getValues().get(0).primitiveValue(); + } + return null; + } + + @Override + public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException { + throw new Error("Not implemented yet: resolveReference"); + } + + @Override + public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException { + throw new Error("Not implemented yet: conformsToProfile"); + } + + @Override + public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { + throw new Error("Not implemented yet: resolveValueSet"); + } + @Override + public boolean paramIsType(String name, int index) { + return "getReferenceKey".equals(name); + } + public List getIssues() { + return issues; + } + + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Storage.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Storage.java new file mode 100644 index 000000000..8d228da5a --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Storage.java @@ -0,0 +1,22 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.util.List; + +import org.hl7.fhir.r4.utils.sql.Validator.TrueFalseOrUnknown; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.utils.sql.Cell; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.Store; + +public interface Storage { + + TrueFalseOrUnknown supportsArrays(); + TrueFalseOrUnknown supportsComplexTypes(); + + Store createStore(String name, List columns); + void addRow(Store store, List cells); + void finish(Store store); + TrueFalseOrUnknown needsName(); + String getKeyForSourceResource(Base res); + String getKeyForTargetResource(Base res); +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageJson.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageJson.java new file mode 100644 index 000000000..3dc845cc8 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageJson.java @@ -0,0 +1,105 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.utils.sql.Validator.TrueFalseOrUnknown; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.utils.sql.Cell; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.Storage; +import org.hl7.fhir.r4.utils.sql.Store; +import org.hl7.fhir.r4.utils.sql.Value; +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonBoolean; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonNull; +import org.hl7.fhir.utilities.json.model.JsonNumber; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.model.JsonString; + +public class StorageJson implements Storage { + + private String name; + private JsonArray rows; + + @Override + public TrueFalseOrUnknown supportsArrays() { + return TrueFalseOrUnknown.TRUE; + } + + @Override + public Store createStore(String name, List columns) { + this.name = name; + this.rows = new JsonArray(); + return new Store(name); // we're not doing anything with this + } + + @Override + public void addRow(Store store, List cells) { + JsonObject row = new JsonObject(); + rows.add(row); + for (Cell cell : cells) { + if (cell.getColumn().isColl() || cell.getValues().size() > 1) { + JsonArray arr = new JsonArray(); + row.add(cell.getColumn().getName(), arr); + for (Value value : cell.getValues()) { + arr.add(makeJsonNode(value)); + } + } else if (cell.getValues().size() == 0) { + row.add(cell.getColumn().getName(), new JsonNull()); + } else { + row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0))); + } + } + } + + private JsonElement makeJsonNode(Value value) { + if (value == null) { + return new JsonNull(); + } else if (value.getValueInt() != null) { + return new JsonNumber(value.getValueInt().intValue()); + } + if (value.getValueBoolean() != null) { + return new JsonBoolean(value.getValueBoolean().booleanValue()); + } + if (value.getValueDecimal() != null) { + return new JsonNumber(value.getValueDecimal().toPlainString()); + } + return new JsonString(value.getValueString()); + } + + @Override + public void finish(Store store) { + // nothing + } + + public String getName() { + return name; + } + + public JsonArray getRows() { + return rows; + } + + @Override + public TrueFalseOrUnknown supportsComplexTypes() { + return TrueFalseOrUnknown.TRUE; + } + + @Override + public TrueFalseOrUnknown needsName() { + return TrueFalseOrUnknown.FALSE; + } + + @Override + public String getKeyForSourceResource(Base res) { + return res.fhirType()+"/"+res.getIdBase(); + } + + @Override + public String getKeyForTargetResource(Base res) { + return res.fhirType()+"/"+res.getIdBase(); + } + +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageSqlite3.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageSqlite3.java new file mode 100644 index 000000000..9dfef5ebf --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/StorageSqlite3.java @@ -0,0 +1,151 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLType; +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.utils.sql.Validator.TrueFalseOrUnknown; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.utils.sql.Cell; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.ColumnKind; +import org.hl7.fhir.r4.utils.sql.Storage; +import org.hl7.fhir.r4.utils.sql.Store; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; + +public class StorageSqlite3 implements Storage { + + public static class SQLiteStore extends Store { + private PreparedStatement p; + + protected SQLiteStore(String name, PreparedStatement p) { + super(name); + this.p = p; + } + + public PreparedStatement getP() { + return p; + } + + } + + private Connection conn; + private int nextKey = 0; + + public StorageSqlite3(Connection conn) { + super(); + this.conn = conn; + } + + @Override + public Store createStore(String name, List columns) { + try { + CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", "); + CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", "); + StringBuilder b = new StringBuilder(); + b.append("Create Table "+name+" ( "); + b.append("ViewRowKey integer NOT NULL"); + for (Column column : columns) { + b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable + fields.append(column.getName()); + values.append("?"); + } + b.append(", PRIMARY KEY (ViewRowKey))\r\n"); + conn.createStatement().execute(b.toString()); + + String isql = "Insert into "+name+" (ViewRowKey, "+fields.toString()+") values (?, "+values.toString()+")"; + PreparedStatement psql = conn.prepareStatement(isql); + return new SQLiteStore(name, psql); + } catch (Exception e) { + throw new FHIRException(e); + } + } + + private String sqliteType(ColumnKind type) { + switch (type) { + case DateTime: return "Text"; + case Decimal: return "Real"; + case Integer: return "Integer"; + case String: return "Text"; + case Time: return "Text"; + case Binary: return "Text"; + case Boolean: return "Integer"; + case Complex: throw new FHIRException("SQLite runner does not handle complexes"); + } + return null; + } + + @Override + public void addRow(Store store, List cells) { + try { + SQLiteStore sqls = (SQLiteStore) store; + PreparedStatement p = sqls.getP(); + p.setInt(1, ++nextKey); + for (int i = 0; i < cells.size(); i++) { + Cell c = cells.get(i); + switch (c.getColumn().getKind()) { + case Null: + p.setNull(i+2, java.sql.Types.NVARCHAR); + case Binary: + p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary()); + break; + case Boolean: + p.setBoolean(i+2, c.getValues().size() == 0 ? false : c.getValues().get(0).getValueBoolean().booleanValue()); + break; + case DateTime: + p.setDate(i+2, c.getValues().size() == 0 ? null : new java.sql.Date(c.getValues().get(0).getValueDate().getTime())); + break; + case Decimal: + p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString()); + break; + case Integer: + p.setInt(i+2, c.getValues().size() == 0 ? 0 : c.getValues().get(0).getValueInt().intValue()); + break; + case String: + p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString()); + break; + case Time: + p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString()); + break; + case Complex: throw new FHIRException("SQLite runner does not handle complexes"); + } + } + p.execute(); + } catch (Exception e) { + throw new FHIRException(e); + } + + } + + @Override + public void finish(Store store) { + // nothing + } + + @Override + public TrueFalseOrUnknown supportsArrays() { + return TrueFalseOrUnknown.FALSE; + } + + @Override + public TrueFalseOrUnknown supportsComplexTypes() { + return TrueFalseOrUnknown.FALSE; + } + + @Override + public TrueFalseOrUnknown needsName() { + return TrueFalseOrUnknown.TRUE; + } + + @Override + public String getKeyForSourceResource(Base res) { + throw new Error("Key management for resources isn't decided yet"); + } + + @Override + public String getKeyForTargetResource(Base res) { + throw new Error("Key management for resources isn't decided yet"); + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Store.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Store.java new file mode 100644 index 000000000..c30757fa7 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Store.java @@ -0,0 +1,19 @@ +package org.hl7.fhir.r4.utils.sql; + +public class Store { + + private String name; + + protected Store(String name) { + super(); + this.name = name; + } + + public String getName() { + return name; + } + + public void flush() { + + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Validator.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Validator.java new file mode 100644 index 000000000..912afcb74 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Validator.java @@ -0,0 +1,717 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nonnull; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.utils.sql.Validator.TrueFalseOrUnknown; +import org.hl7.fhir.r4.context.IWorkerContext; +import org.hl7.fhir.r4.fhirpath.ExpressionNode; +import org.hl7.fhir.r4.fhirpath.FHIRPathEngine; +import org.hl7.fhir.r4.fhirpath.TypeDetails; +import org.hl7.fhir.r4.formats.JsonParser; +import org.hl7.fhir.r4.model.Base64BinaryType; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.OidType; +import org.hl7.fhir.r4.model.PositiveIntType; +import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.TimeType; +import org.hl7.fhir.r4.model.UnsignedIntType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.UrlType; +import org.hl7.fhir.r4.model.UuidType; +import org.hl7.fhir.r4.utils.sql.Column; +import org.hl7.fhir.r4.utils.sql.ColumnKind; +import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r4.fhirpath.FHIRPathEngine.IssueMessage; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.model.JsonArray; +import org.hl7.fhir.utilities.json.model.JsonBoolean; +import org.hl7.fhir.utilities.json.model.JsonElement; +import org.hl7.fhir.utilities.json.model.JsonNumber; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.model.JsonProperty; +import org.hl7.fhir.utilities.json.model.JsonString; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; +import org.hl7.fhir.utilities.validation.ValidationMessage.Source; + +public class Validator { + + public enum TrueFalseOrUnknown { + TRUE, FALSE, UNKNOWN + } + + private IWorkerContext context; + private FHIRPathEngine fpe; + private List prohibitedNames = new ArrayList(); + private List issues = new ArrayList(); + private TrueFalseOrUnknown supportsArrays; + private TrueFalseOrUnknown supportsComplexTypes; + private TrueFalseOrUnknown supportsNeedsName; + + private String resourceName; + private String name; + + public Validator(IWorkerContext context, FHIRPathEngine fpe, List prohibitedNames, @Nonnull TrueFalseOrUnknown supportsArrays, @Nonnull TrueFalseOrUnknown supportsComplexTypes, @Nonnull TrueFalseOrUnknown supportsNeedsName) { + super(); + this.context = context; + this.fpe = fpe; + this.prohibitedNames = prohibitedNames; + this.supportsArrays = supportsArrays; + this.supportsComplexTypes = supportsComplexTypes; + this.supportsNeedsName = supportsNeedsName; + } + + public String getResourceName() { + return resourceName; + } + + + public void checkViewDefinition(String path, JsonObject viewDefinition) { + checkProperties(viewDefinition, path, "resourceType", "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where"); + + JsonElement nameJ = viewDefinition.get("name"); + if (nameJ == null) { + if (supportsNeedsName == null) { + hint(path, viewDefinition, "No name provided. A name is required in many contexts where a ViewDefinition is used"); + } else if (supportsNeedsName == TrueFalseOrUnknown.TRUE) { + error(path, viewDefinition, "No name provided", IssueType.REQUIRED); + } + } else if (!(nameJ instanceof JsonString)) { + error(path, viewDefinition, "name must be a string", IssueType.INVALID); + } else { + name = nameJ.asString(); + if (!isValidName(name)) { + error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT); + } + if (prohibitedNames.contains(name)) { + error(path, nameJ, "The name '"+name+"' on the viewDefinition is not allowed in this context", IssueType.BUSINESSRULE); + } + } + + List columns = new ArrayList<>(); + viewDefinition.setUserData("columns", columns); + + JsonElement resourceNameJ = viewDefinition.get("resource"); + if (resourceNameJ == null) { + error(path, viewDefinition, "No resource specified", IssueType.REQUIRED); + } else if (!(resourceNameJ instanceof JsonString)) { + error(path, viewDefinition, "resource must be a string", IssueType.INVALID); + } else { + resourceName = resourceNameJ.asString(); + if (!context.getResourceNamesAsSet().contains(resourceName)) { + error(path+".name", nameJ, "The name '"+resourceName+"' is not a valid resource", IssueType.BUSINESSRULE); + } else { + int i = 0; + if (checkAllObjects(path, viewDefinition, "constant")) { + for (JsonObject constant : viewDefinition.getJsonObjects("constant")) { + checkConstant(path+".constant["+i+"]", constant); + i++; + } + } + i = 0; + if (checkAllObjects(path, viewDefinition, "where")) { + for (JsonObject where : viewDefinition.getJsonObjects("where")) { + checkWhere(viewDefinition, path+".where["+i+"]", where); + i++; + } + } + TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName); + + i = 0; + if (checkAllObjects(path, viewDefinition, "select")) { + for (JsonObject select : viewDefinition.getJsonObjects("select")) { + columns.addAll(checkSelect(viewDefinition, path+".select["+i+"]", select, t)); + i++; + } + if (i == 0) { + error(path, viewDefinition, "No select statements found", IssueType.REQUIRED); + } + } + } + } + } + + private List checkSelect(JsonObject vd, String path, JsonObject select, TypeDetails t) { + List columns = new ArrayList<>(); + select.setUserData("columns", columns); + checkProperties(select, path, "column", "select", "forEach", "forEachOrNull", "unionAll"); + + if (select.has("forEach")) { + t = checkForEach(vd, path, select, select.get("forEach"), t); + } else if (select.has("forEachOrNull")) { + t = checkForEachOrNull(vd, path, select, select.get("forEachOrNull"), t); + } + + if (t != null) { + + if (select.has("column")) { + JsonElement a = select.get("column"); + if (!(a instanceof JsonArray)) { + error(path+".column", a, "column is not an array", IssueType.INVALID); + } else { + int i = 0; + for (JsonElement e : ((JsonArray) a)) { + if (!(e instanceof JsonObject)) { + error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID); + } else { + columns.add(checkColumn(vd, path+".column["+i+"]", (JsonObject) e, t)); + } + } + } + } + + if (select.has("select")) { + JsonElement a = select.get("select"); + if (!(a instanceof JsonArray)) { + error(path+".select", a, "select is not an array", IssueType.INVALID); + } else { + int i = 0; + for (JsonElement e : ((JsonArray) a)) { + if (!(e instanceof JsonObject)) { + error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID); + } else { + columns.addAll(checkSelect(vd, path+".select["+i+"]", (JsonObject) e, t)); + } + } + } + } + + if (select.has("unionAll")) { + columns.addAll(checkUnion(vd, path, select, select.get("unionAll"), t)); + } + if (columns.isEmpty()) { + error(path, select, "The select has no columns or selects", IssueType.REQUIRED); + } else { + checkColumnNamesUnique(select, path, columns); + } + } + return columns; + } + + + private void checkColumnNamesUnique(JsonObject select, String path, List columns) { + Set names = new HashSet<>(); + for (Column col : columns) { + if (col != null) { + if (!names.contains(col.getName())) { + names.add(col.getName()); + } else if (!col.isDuplicateReported()) { + col.setDuplicateReported(true); + error(path, select, "Duplicate Column Name '"+col.getName()+"'", IssueType.BUSINESSRULE); + } + } + } + } + + private List checkUnion(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) { + JsonElement a = focus.get("unionAll"); + if (!(a instanceof JsonArray)) { + error(path+".unionAll", a, "union is not an array", IssueType.INVALID); + return null; + } else { + List> unionColumns = new ArrayList<>(); + int i = 0; + for (JsonElement e : ((JsonArray) a)) { + if (!(e instanceof JsonObject)) { + error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID); + } else { + unionColumns.add(checkSelect(vd, path+".unionAll["+i+"]", (JsonObject) e, t)); + } + i++; + } + if (i < 2) { + warning(path+".unionAll", a, "unionAll should have more than one item"); + } + if (unionColumns.size() > 1) { + List columns = unionColumns.get(0); + for (int ic = 1; ic < unionColumns.size(); ic++) { + String diff = columnDiffs(columns, unionColumns.get(ic)); + if (diff != null) { + error(path+".unionAll["+i+"]", ((JsonArray) a).get(ic), "unionAll["+i+"] column definitions do not match: "+diff, IssueType.INVALID); + } + } + a.setUserData("colunms", columns); + return columns; + } + } + return null; + } + + private String columnDiffs(List list1, List list2) { + if (list1.size() == list2.size()) { + for (int i = 0; i < list1.size(); i++) { + if (list1.get(i) == null || list2.get(i) == null) { + return null; // just suppress any addition errors + } + String diff = list1.get(i).diff(list2.get(i)); + if (diff != null) { + return diff+" at #"+i; + } + } + return null; + } else { + return "Column counts differ: "+list1.size()+" vs "+list2.size(); + } + } + + private Column checkColumn(JsonObject vd, String path, JsonObject column, TypeDetails t) { + checkProperties(column, path, "path", "name", "description", "collection", "type", "tag"); + + if (!column.has("path")) { + error(path, column, "no path found", IssueType.INVALID); + } else { + JsonElement expression = column.get("path"); + if (!(expression instanceof JsonString)) { + error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID); + } else { + String expr = expression.asString(); + + List warnings = new ArrayList<>(); + TypeDetails td = null; + ExpressionNode node = null; + try { + node = fpe.parse(expr); + column.setUserData("path", node); + td = fpe.checkOnTypes(vd, resourceName, t, node, warnings); + } catch (Exception e) { + error(path, expression, e.getMessage(), IssueType.INVALID); + } + if (td != null && node != null) { + for (IssueMessage s : warnings) { + warning(path+".path", expression, s.getMessage()); + } + String columnName = null; + JsonElement nameJ = column.get("name"); + if (nameJ != null) { + if (nameJ instanceof JsonString) { + columnName = nameJ.asString(); + if (!isValidName(columnName)) { + error(path+".name", nameJ, "The name '"+columnName+"' is not valid", IssueType.VALUE); + } + } else { + error(path+".name", nameJ, "name must be a string", IssueType.INVALID); + } + } + if (columnName == null) { + List names = node.getDistalNames(); + if (names.size() == 1 && names.get(0) != null) { + columnName = names.get(0); + if (!isValidName(columnName)) { + error(path+".path", expression, "A column name is required. The natural name to chose is '"+columnName+"' (from the path)", IssueType.INVARIANT); + } else { + error(path, column, "A column name is required", IssueType.REQUIRED); + } + } else { + error(path, column, "A column name is required", IssueType.REQUIRED); + } + } + // ok, name is sorted! + if (columnName != null) { + column.setUserData("name", columnName); + boolean isColl = false; + if (column.has("collection")) { + JsonElement collectionJ = column.get("collection"); + if (!(collectionJ instanceof JsonBoolean)) { + error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID); + } else { + boolean collection = collectionJ.asJsonBoolean().asBoolean(); + if (collection) { + isColl = true; + } + } + } + if (isColl) { + if (td.getCollectionStatus() == CollectionStatus.SINGLETON) { + hint(path, column, "collection is true, but the path statement(s) ('"+expr+"') can only return single values for the column '"+columnName+"'"); + } + if (supportsArrays == TrueFalseOrUnknown.UNKNOWN) { + warning(path, expression, "The column '"+columnName+"' is defined as a collection, but collections are not supported in all execution contexts"); + } else if (supportsArrays == TrueFalseOrUnknown.FALSE) { + if (td.getCollectionStatus() == CollectionStatus.SINGLETON) { + warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can only return a single value"); + } else { + warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can return a collection of values"); + } + } + } else { + if (td.getCollectionStatus() != CollectionStatus.SINGLETON) { + warning(path, column, "This column is not defined as a collection, but the path statement '"+expr+"' might return multiple values for the column '"+columnName+"' for some inputs"); + } + } + Set types = new HashSet<>(); + if (node.isNullSet()) { + types.add("null"); + } else { + // ok collection is sorted + for (String type : td.getTypes()) { + types.add(simpleType(type)); + } + + JsonElement typeJ = column.get("type"); + if (typeJ != null) { + if (typeJ instanceof JsonString) { + String type = typeJ.asString(); + if (!td.hasType(type)) { + error(path+".type", typeJ, "The path expression ('"+expr+"') does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE); + } else { + types.clear(); + types.add(simpleType(type)); + } + } else { + error(path+".type", typeJ, "type must be a string", IssueType.INVALID); + } + } + } + if (types.size() != 1) { + error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE); + } else { + String type = types.iterator().next(); + boolean ok = false; + if (!isSimpleType(type) && !"null".equals(type)) { + if (supportsComplexTypes == TrueFalseOrUnknown.UNKNOWN) { + warning(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"'). This is not supported in some Runners"); + } else if (supportsComplexTypes == TrueFalseOrUnknown.FALSE) { + error(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"') but this is not allowed in this context", IssueType.BUSINESSRULE); + } else { + ok = true; + } + } else { + ok = true; + } + if (ok) { + Column col = new Column(columnName, isColl, type, kindForType(type)); + column.setUserData("column", col); + return col; + } + } + } + } + } + } + return null; + } + + private ColumnKind kindForType(String type) { + switch (type) { + case "null": return ColumnKind.Null; + case "dateTime": return ColumnKind.DateTime; + case "boolean": return ColumnKind.Boolean; + case "integer": return ColumnKind.Integer; + case "decimal": return ColumnKind.Decimal; + case "string": return ColumnKind.String; + case "canonical": return ColumnKind.String; + case "url": return ColumnKind.String; + case "uri": return ColumnKind.String; + case "oid": return ColumnKind.String; + case "uuid": return ColumnKind.String; + case "id": return ColumnKind.String; + case "code": return ColumnKind.String; + case "base64Binary": return ColumnKind.Binary; + case "time": return ColumnKind.Time; + default: return ColumnKind.Complex; + } + } + + private boolean isSimpleType(String type) { + return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time", "canonical"); + } + + private String simpleType(String type) { + type = type.replace("http://hl7.org/fhirpath/System.", "").replace("http://hl7.org/fhir/StructureDefinition/", ""); + if (Utilities.existsInList(type, "date", "dateTime", "instant")) { + return "dateTime"; + } + if (Utilities.existsInList(type, "Boolean", "boolean")) { + return "boolean"; + } + if (Utilities.existsInList(type, "Integer", "integer", "integer64")) { + return "integer"; + } + if (Utilities.existsInList(type, "Decimal", "decimal")) { + return "decimal"; + } + if (Utilities.existsInList(type, "String", "string", "code")) { + return "string"; + } + if (Utilities.existsInList(type, "Time", "time")) { + return "time"; + } + if (Utilities.existsInList(type, "base64Binary")) { + return "base64Binary"; + } + return type; + } + + private TypeDetails checkForEach(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) { + if (!(expression instanceof JsonString)) { + error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID); + return null; + } else { + String expr = expression.asString(); + + List warnings = new ArrayList<>(); + TypeDetails td = null; + try { + ExpressionNode n = fpe.parse(expr); + focus.setUserData("forEach", n); + td = fpe.checkOnTypes(vd, resourceName, t, n, warnings); + } catch (Exception e) { + error(path, expression, e.getMessage(), IssueType.INVALID); + } + if (td != null) { + for (IssueMessage s : warnings) { + warning(path+".forEach", expression, s.getMessage()); + } + } + return td; + } + } + + private TypeDetails checkForEachOrNull(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) { + if (!(expression instanceof JsonString)) { + error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID); + return null; + } else { + String expr = expression.asString(); + + List warnings = new ArrayList<>(); + TypeDetails td = null; + try { + ExpressionNode n = fpe.parse(expr); + focus.setUserData("forEachOrNull", n); + td = fpe.checkOnTypes(vd, resourceName, t, n, warnings); + } catch (Exception e) { + error(path, expression, e.getMessage(), IssueType.INVALID); + } + if (td != null) { + for (IssueMessage s : warnings) { + warning(path+".forEachOrNull", expression, s.getMessage()); + } + } + return td; + } + } + + private void checkConstant(String path, JsonObject constant) { + checkProperties(constant, path, "name", "valueBase64Binary", "valueBoolean", "valueCanonical", "valueCode", "valueDate", "valueDateTime", "valueDecimal", "valueId", "valueInstant", "valueInteger", "valueInteger64", "valueOid", "valueString", "valuePositiveInt", "valueTime", "valueUnsignedInt", "valueUri", "valueUrl", "valueUuid"); + JsonElement nameJ = constant.get("name"); + if (nameJ == null) { + error(path, constant, "No name provided", IssueType.REQUIRED); + } else if (!(nameJ instanceof JsonString)) { + error(path, constant, "Name must be a string", IssueType.INVALID); + } else { + String name = constant.asString("name"); + if (!isValidName(name)) { + error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT); + } + } + if (constant.has("valueBase64Binary")) { + checkIsString(path, constant, "valueBase64Binary", new Base64BinaryType()); + } else if (constant.has("valueBoolean")) { + checkIsBoolean(path, constant, "valueBoolean", new BooleanType()); + } else if (constant.has("valueCanonical")) { + checkIsString(path, constant, "valueCanonical", new CanonicalType()); + } else if (constant.has("valueCode")) { + checkIsString(path, constant, "valueCode", new CodeType()); + } else if (constant.has("valueDate")) { + checkIsString(path, constant, "valueDate", new DateType()); + } else if (constant.has("valueDateTime")) { + checkIsString(path, constant, "valueDateTime", new DateTimeType()); + } else if (constant.has("valueDecimal")) { + checkIsNumber(path, constant, "valueDecimal", new DecimalType()); + } else if (constant.has("valueId")) { + checkIsString(path, constant, "valueId", new IdType()); + } else if (constant.has("valueInstant")) { + checkIsString(path, constant, "valueInstant", new InstantType()); + } else if (constant.has("valueInteger")) { + checkIsNumber(path, constant, "valueInteger", new IntegerType()); + } else if (constant.has("valueOid")) { + checkIsString(path, constant, "valueOid", new OidType()); + } else if (constant.has("valueString")) { + checkIsString(path, constant, "valueString", new StringType()); + } else if (constant.has("valuePositiveInt")) { + checkIsNumber(path, constant, "valuePositiveInt", new PositiveIntType()); + } else if (constant.has("valueTime")) { + checkIsString(path, constant, "valueTime", new TimeType()); + } else if (constant.has("valueUnsignedInt")) { + checkIsNumber(path, constant, "valueUnsignedInt", new UnsignedIntType()); + } else if (constant.has("valueUri")) { + checkIsString(path, constant, "valueUri", new UriType()); + } else if (constant.has("valueUrl")) { + checkIsString(path, constant, "valueUrl", new UrlType()); + } else if (constant.has("valueUuid")) { + checkIsString(path, constant, "valueUuid", new UuidType()); + } else { + error(path, constant, "No value found", IssueType.REQUIRED); + } + } + + private void checkIsString(String path, JsonObject constant, String name, PrimitiveType value) { + JsonElement j = constant.get(name); + if (!(j instanceof JsonString)) { + error(path+"."+name, j, name+" must be a string", IssueType.INVALID); + } else { + value.setValueAsString(j.asString()); + constant.setUserData("value", value); + } + } + + private void checkIsBoolean(String path, JsonObject constant, String name, PrimitiveType value) { + JsonElement j = constant.get(name); + if (!(j instanceof JsonBoolean)) { + error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID); + } else { + value.setValueAsString(j.asString()); + constant.setUserData("value", value); + } + } + + private void checkIsNumber(String path, JsonObject constant, String name, PrimitiveType value) { + JsonElement j = constant.get(name); + if (!(j instanceof JsonNumber)) { + error(path+"."+name, j, name+" must be a number", IssueType.INVALID); + } else { + value.setValueAsString(j.asString()); + constant.setUserData("value", value); + } + } + + private void checkWhere(JsonObject vd, String path, JsonObject where) { + checkProperties(where, path, "path", "description"); + + String expr = where.asString("path"); + if (expr == null) { + error(path, where, "No path provided", IssueType.REQUIRED); + } + List types = new ArrayList<>(); + List warnings = new ArrayList<>(); + types.add(resourceName); + TypeDetails td = null; + try { + ExpressionNode n = fpe.parse(expr); + where.setUserData("path", n); + td = fpe.checkOnTypes(vd, resourceName, types, n, warnings); + } catch (Exception e) { + error(path, where.get("path"), e.getMessage(), IssueType.INVALID); + } + if (td != null) { + if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) { + error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE); + } else { + for (IssueMessage s : warnings) { + warning(path+".path", where.get("path"), s.getMessage()); + } + } + } + } + + private void checkProperties(JsonObject obj, String path, String... names) { + for (JsonProperty p : obj.getProperties()) { + boolean nameOk = "extension".equals(p.getName()); + for (String name : names) { + nameOk = nameOk || name.equals(p.getName()); + } + if (!nameOk) { + error(path+"."+p.getName(), p.getValue(), "Unknown JSON property "+p.getName(), IssueType.UNKNOWN); + } + } + + } + + private boolean isValidName(String name) { + boolean first = true; + for (char c : name.toCharArray()) { + if (!(Character.isAlphabetic(c) || Character.isDigit(c) || (!first && c == '_'))) { + return false; + } + first = false; + } + return true; + } + + + private boolean checkAllObjects(String path, JsonObject focus, String name) { + if (!focus.has(name)) { + return true; + } else if (!(focus.get(name) instanceof JsonArray)) { + error(path+"."+name, focus.get(name), name+" must be an array", IssueType.INVALID); + return false; + } else { + JsonArray arr = focus.getJsonArray(name); + int i = 0; + boolean ok = true; + for (JsonElement e : arr) { + if (!(e instanceof JsonObject)) { + error(path+"."+name+"["+i+"]", e, name+"["+i+"] must be an object", IssueType.INVALID); + ok = false; + } + } + return ok; + } + } + + private void error(String path, JsonElement e, String issue, IssueType type) { + ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, type, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.ERROR); + issues.add(vm); + + } + + private void warning(String path, JsonElement e, String issue) { + ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.WARNING); + issues.add(vm); + } + + private void hint(String path, JsonElement e, String issue) { + ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.INFORMATION); + issues.add(vm); + } + + public void dump() { + for (ValidationMessage vm : issues) { + System.out.println(vm.summary()); + } + + } + + public void check() { + if (!isOk()) { + throw new FHIRException("View Definition is not valid"); + } + + } + + public String getName() { + return name; + } + + public List getIssues() { + return issues; + } + + public boolean isOk() { + boolean ok = true; + for (ValidationMessage vm : issues) { + if (vm.isError()) { + ok = false; + } + } + return ok; + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Value.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Value.java new file mode 100644 index 000000000..bd863d066 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/sql/Value.java @@ -0,0 +1,130 @@ +package org.hl7.fhir.r4.utils.sql; + +import java.math.BigDecimal; +import java.util.Date; + +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.utils.sql.Value; + + +/** + * String value is always provided, and a more specific value may also be provided + */ + +public class Value { + + private String valueString; + private Boolean valueBoolean; + private Date valueDate; + private Integer valueInt; + private BigDecimal valueDecimal; + private byte[] valueBinary; + private Base valueComplex; + + private Value() { + super(); + } + + public static Value makeString(String s) { + Value v = new Value(); + v.valueString = s; + return v; + } + + public static Value makeBoolean(String s, Boolean b) { + Value v = new Value(); + v.valueString = s; + v.valueBoolean = b; + return v; + } + + public static Value makeDate(String s, Date d) { + Value v = new Value(); + v.valueString = s; + v.valueDate = d; + return v; + } + + public static Value makeInteger(String s, Integer i) { + Value v = new Value(); + v.valueString = s; + v.valueInt = i; + return v; + } + + + public static Value makeDecimal(String s, BigDecimal bigDecimal) { + Value v = new Value(); + v.valueString = s; + v.valueDecimal = bigDecimal; + return v; + } + + public static Value makeBinary(String s, byte[] b) { + Value v = new Value(); + v.valueString = s; + v.valueBinary = b; + return v; + } + + public static Value makeComplex(Base b) { + Value v = new Value(); + v.valueComplex = b; + return v; + } + public String getValueString() { + return valueString; + } + + public Date getValueDate() { + return valueDate; + } + + public Integer getValueInt() { + return valueInt; + } + + public BigDecimal getValueDecimal() { + return valueDecimal; + } + + public byte[] getValueBinary() { + return valueBinary; + } + + public Boolean getValueBoolean() { + return valueBoolean; + } + + public Base getValueComplex() { + return valueComplex; + } + + public boolean hasValueString() { + return valueString != null; + } + + public boolean hasValueDate() { + return valueDate != null; + } + + public boolean hasValueInt() { + return valueInt != null; + } + + public boolean hasValueDecimal() { + return valueDecimal != null; + } + + public boolean hasValueBinary() { + return valueBinary != null; + } + + public boolean hasValueBoolean() { + return valueBoolean != null; + } + + public boolean hasValueComplex() { + return valueComplex != null; + } +} diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRPathTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRPathTests.java index e81ecf1d0..04dc62e91 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRPathTests.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRPathTests.java @@ -117,6 +117,10 @@ public class FHIRPathTests { return TestingUtilities.context().fetchResource(ValueSet.class, url); } + @Override + public boolean paramIsType(String name, int index) { + return false; + } } private static FHIRPathEngine fp; diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/SnapShotGenerationTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/SnapShotGenerationTests.java index 86e93fa7e..ebc25d69e 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/SnapShotGenerationTests.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/SnapShotGenerationTests.java @@ -249,6 +249,11 @@ public class SnapShotGenerationTests { return null; } + @Override + public boolean isPrimitiveType(String name) { + StructureDefinition sd = TestingUtilities.context().fetchTypeDefinition(name); + return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE); + } } private static class SnapShotGenerationTestsContext implements IEvaluationContext { @@ -386,6 +391,10 @@ public class SnapShotGenerationTests { throw new Error("Not implemented yet"); } + @Override + public boolean paramIsType(String name, int index) { + return false; + } } private static FHIRPathEngine fp; diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRLexer.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRLexer.java index 8f2138ad9..86f7fb087 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRLexer.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/fhirpath/FHIRLexer.java @@ -58,26 +58,26 @@ public class FHIRLexer { private boolean allowDoubleQuotes; public FHIRLexer(String source, String name) throws FHIRLexerException { - this.source = source == null ? "" : source; + this.source = source == null ? "" : Utilities.stripBOM(source); this.name = name == null ? "??" : name; currentLocation = new SourceLocation(1, 1); next(); } public FHIRLexer(String source, int i) throws FHIRLexerException { - this.source = source; + this.source = Utilities.stripBOM(source); this.cursor = i; currentLocation = new SourceLocation(1, 1); next(); } public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException { - this.source = source; + this.source = Utilities.stripBOM(source); this.cursor = i; this.allowDoubleQuotes = allowDoubleQuotes; currentLocation = new SourceLocation(1, 1); next(); } public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException { - this.source = source == null ? "" : source; + this.source = source == null ? "" : Utilities.stripBOM(source); this.name = name == null ? "??" : name; this.metadataFormat = metadataFormat; this.allowDoubleQuotes = allowDoubleQuotes; @@ -187,7 +187,7 @@ public class FHIRLexer { cursor++; } else while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || - (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-')) + (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_')) cursor++; current = source.substring(currentStart, cursor); } else if (ch == '/') { @@ -455,7 +455,7 @@ public class FHIRLexer { i = i + 4; break; default: - throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation); + throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation); } } else { b.append(ch); @@ -499,12 +499,12 @@ public class FHIRLexer { break; case 'u': i++; - int uc = Integer.parseInt(s.substring(i, i+4), 16); + int uc = Integer.parseInt(s.substring(i, i+4), 32); b.append(Character.toString(uc)); i = i + 4; break; default: - throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation); + throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation); } } else { b.append(ch);