From 11c887edc5cfbf27386455cf18c6bc8a772449b7 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 10 Feb 2023 10:02:40 +1100 Subject: [PATCH 1/2] FHIR-34417: implementations for comparable(), highBoundary(), lowBoundary() --- .../org/hl7/fhir/r5/elementmodel/Element.java | 33 +++++++++ .../main/java/org/hl7/fhir/r5/model/Base.java | 2 + .../org/hl7/fhir/r5/model/ExpressionNode.java | 4 +- .../java/org/hl7/fhir/r5/model/Tuple.java | 7 ++ .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 67 ++++++++++++++++++- .../org/hl7/fhir/r5/utils/GraphQLEngine.java | 10 +++ 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java index bd4f04a37..9fc7d079d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java @@ -1314,5 +1314,38 @@ public class Element extends Base { throw new Error("Unrecognised name "+name+" on "+this.name); } + + @Override + public Base copy() { + Element element = new Element(this); + this.copyValues(element); + return element; + } + + @Override + public void copyValues(Base dst) { + super.copyValues(dst); + + Element dest = (Element) dst; + dest.comments.clear(); + dest.comments.addAll(comments); + dest.value = value; + dest.children.clear(); + dest.children.addAll(children); + dest.line = line; + dest.col = col; + dest.xhtml = xhtml; + dest.explicitType = explicitType; + dest.hasParentForValidator = false; + dest.path = path; + dest.messages.clear(); + dest.prohibited = prohibited; + dest.required = required; + dest.childMap.clear(); + dest.descendentCount = descendentCount; + dest.instanceId = instanceId; + dest.isNull = isNull; + dest.source = source; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java index 3ffcab8bf..0aeba8345 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java @@ -420,6 +420,8 @@ public abstract class Base implements Serializable, IBase, IElement { return null; } + public abstract Base copy(); + public void copyValues(Base dst) { } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java index 0a3ae3746..e5af0adef 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java @@ -56,7 +56,7 @@ public class ExpressionNode { Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, // Local extensions to FHIRPath - HtmlChecks1, HtmlChecks2, AliasAs, Alias; + HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; public static Function fromCode(String name) { if (name.equals("empty")) return Function.Empty; @@ -118,6 +118,7 @@ public class ExpressionNode { 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; @@ -227,6 +228,7 @@ public class ExpressionNode { 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"; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Tuple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Tuple.java index ddffee042..0fff6567e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Tuple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Tuple.java @@ -98,5 +98,12 @@ public class Tuple extends Base { return getProperty(name.hashCode(), name, checkValid); } + @Override + public Base copy() { + Tuple tuple = new Tuple(); + copyValues(tuple); + return tuple; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index f8f9f1d2a..92d42ee66 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -147,6 +147,11 @@ public class FHIRPathEngine { public String primitiveValue() { return value; } + + @Override + public Base copy() { + throw new Error("Not Implemented"); + } } private class ClassTypeInfo extends Base { @@ -205,6 +210,11 @@ public class FHIRPathEngine { return instance.fhirType(); } } + + @Override + public Base copy() { + throw new Error("Not Implemented"); + } } public static class TypedElementDefinition { @@ -1403,6 +1413,7 @@ public class FHIRPathEngine { 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); @@ -3335,6 +3346,8 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case HtmlChecks2 : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case Comparable : + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Alias : checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return anything(CollectionStatus.SINGLETON); @@ -3551,7 +3564,7 @@ public class FHIRPathEngine { } private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) { + if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity")) { throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); } } @@ -3641,6 +3654,7 @@ public class FHIRPathEngine { case Alias : return funcAlias(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); @@ -3903,6 +3917,11 @@ public class FHIRPathEngine { result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); } else if (base.hasType("time")) { result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision))); + } else if (base.hasType("Quantity")) { + String value = getNamedValue(base, "value"); + Base v = base.copy(); + v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == 0 ? 8 : precision))); + result.add(v); } else { makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); } @@ -3933,6 +3952,11 @@ public class FHIRPathEngine { result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); } else if (base.hasType("time")) { result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision))); + } else if (base.hasType("Quantity")) { + String value = getNamedValue(base, "value"); + Base v = base.copy(); + v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == 0 ? 8 : precision))); + result.add(v); } else { makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); } @@ -4167,6 +4191,47 @@ public class FHIRPathEngine { return false; } + private List funcComparable(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) { + 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); + } + 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); + } + if (u1 == null || u2 == null) { + return makeBoolean(false); + } + if (u1.equals(u2)) { + return makeBoolean(true); + } + if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) { + try { + return makeBoolean(worker.getUcumService().isComparable(u1, u2)); + } catch (UcumException e) { + return makeBoolean(false); + } + } else { + return makeBoolean(false); + } + } + + + private String getNamedValue(Base base, String name) { + Property p = base.getChildByName(name); + if (p.hasValues() && p.getValues().size() == 1) { + return p.getValues().get(0).primitiveValue(); + } + return null; + } private boolean checkHtmlNames(XhtmlNode node) { if (node.getNodeType() == NodeType.Comment) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java index e4c93f55a..741203d27 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java @@ -106,6 +106,11 @@ public class GraphQLEngine implements IGraphQLEngine { default: return super.getNamedProperty(_hash, _name, _checkValid); } } + + @Override + public Base copy() { + throw new Error("Not Implemented"); + } } public static class SearchWrapper extends Base { @@ -195,6 +200,11 @@ public class GraphQLEngine implements IGraphQLEngine { return null; } + @Override + public Base copy() { + throw new Error("Not Implemented"); + } + } private IWorkerContext context; From bd424e7b60d49f97364e5f67b808d78b35bcf37f Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 10 Feb 2023 16:18:33 +1100 Subject: [PATCH 2/2] fixes after more testing --- .../conformance/profile/ProfileUtilities.java | 6 +++++ .../org/hl7/fhir/r5/elementmodel/Element.java | 26 ++++++++++++++----- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 10 +++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 554dc8608..6203ef67c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -1662,6 +1662,9 @@ public class ProfileUtilities extends TranslatingUtilities { if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames)) { b.append("]("); b.append(basePath); + if (!Utilities.noString(basePath) && !basePath.endsWith("/")) { + b.append("/"); + } i = i + 1; } else { b.append("]("); @@ -1740,8 +1743,11 @@ public class ProfileUtilities extends TranslatingUtilities { url.startsWith("loinc.html") || url.startsWith("http.html") || url.startsWith("references") || + url.startsWith("license.html") || url.startsWith("narrative.html") || url.startsWith("search.html") || + url.startsWith("security.html") || + url.startsWith("versions.html") || url.startsWith("patient-operation-match.html") || (url.startsWith("extension-") && url.contains(".html")) || url.startsWith("resource-definitions.html"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java index 9fc7d079d..93f0ce092 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java @@ -1327,25 +1327,39 @@ public class Element extends Base { super.copyValues(dst); Element dest = (Element) dst; - dest.comments.clear(); - dest.comments.addAll(comments); + if (comments != null) { + dest.comments = new ArrayList<>(); + dest.comments.addAll(comments); + } else { + dest.comments = null; + } dest.value = value; - dest.children.clear(); - dest.children.addAll(children); + if (children != null) { + dest.children = new ArrayList<>(); + dest.children.addAll(children); + } else { + dest.children = null; + } dest.line = line; dest.col = col; dest.xhtml = xhtml; dest.explicitType = explicitType; dest.hasParentForValidator = false; dest.path = path; - dest.messages.clear(); + dest.messages = null; dest.prohibited = prohibited; dest.required = required; - dest.childMap.clear(); + dest.childMap = null; dest.descendentCount = descendentCount; dest.instanceId = instanceId; dest.isNull = isNull; dest.source = source; } + public Base setProperty(String name, Base value) throws FHIRException { + setChildValue(name, value.primitiveValue()); + return this; + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 92d42ee66..238a1e87b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -3894,7 +3894,10 @@ public class FHIRPathEngine { } private List funcLowBoundary(ExecutionContext context, List focus, ExpressionNode expr) { - if (focus.size() != 1) { + if (focus.size() == 0) { + return makeNull(); + } + if (focus.size() > 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); } int precision = 0; @@ -3929,7 +3932,10 @@ public class FHIRPathEngine { } private List funcHighBoundary(ExecutionContext context, List focus, ExpressionNode expr) { - if (focus.size() != 1) { + if (focus.size() == 0) { + return makeNull(); + } + if (focus.size() > 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); } int precision = 0;