From 67255636004093f144d22bb86f88540aa21ae068 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 10 Jan 2023 07:16:08 +1100 Subject: [PATCH 1/9] Update R4 FHIRPath implementation from R5, and get all tests passing --- .../main/java/org/hl7/fhir/r4/model/Base.java | 4 + .../hl7/fhir/r4/model/BaseDateTimeType.java | 218 +- .../org/hl7/fhir/r4/model/ExpressionNode.java | 75 +- .../java/org/hl7/fhir/r4/model/Property.java | 12 + .../java/org/hl7/fhir/r4/model/TimeType.java | 71 + .../org/hl7/fhir/r4/model/TypeDetails.java | 6 +- .../fhir/r4/test/utils/TestingUtilities.java | 35 + .../hl7/fhir/r4/utils/FHIRPathConstant.java | 20 + .../org/hl7/fhir/r4/utils/FHIRPathEngine.java | 4109 ++++++++++++----- .../org/hl7/fhir/r4/utils/LiquidEngine.java | 4 +- .../fhir/r4/utils/StructureMapUtilities.java | 2 +- .../org/hl7/fhir/r4/test/FHIRPathTests.java | 217 +- .../fhir/r4/test/SnapShotGenerationTests.java | 2 +- 13 files changed, 3358 insertions(+), 1417 deletions(-) create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathConstant.java 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 dbc897fb5..13f3c447a 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 @@ -849,4 +849,8 @@ private Map userData; return null; } + public XhtmlNode getXhtml() { + return null; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java index c390e7a92..d027ed468 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java @@ -45,7 +45,9 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import ca.uhn.fhir.parser.DataFormatException; + import org.hl7.fhir.utilities.DateTimeUtil; +import org.hl7.fhir.utilities.Utilities; public abstract class BaseDateTimeType extends PrimitiveType { @@ -286,6 +288,13 @@ public abstract class BaseDateTimeType extends PrimitiveType { return getFieldValue(Calendar.MONTH); } + public float getSecondsMilli() { + int sec = getSecond(); + int milli = getMillis(); + String s = Integer.toString(sec)+"."+Utilities.padLeft(Integer.toString(milli), '0', 3); + return Float.parseFloat(s); + } + /** * Returns the nanoseconds within the current second *

@@ -867,84 +876,157 @@ public abstract class BaseDateTimeType extends PrimitiveType { * */ public Boolean equalsUsingFhirPathRules(BaseDateTimeType theOther) { - - BaseDateTimeType me = this; - - // Per FHIRPath rules, we compare equivalence at the lowest precision of the two values, - // so if we need to, we'll clone either side and reduce its precision - int lowestPrecision = Math.min(me.getPrecision().ordinal(), theOther.getPrecision().ordinal()); - TemporalPrecisionEnum lowestPrecisionEnum = TemporalPrecisionEnum.values()[lowestPrecision]; - if (me.getPrecision() != lowestPrecisionEnum) { - me = new DateTimeType(me.getValueAsString()); - me.setPrecision(lowestPrecisionEnum); - } - if (theOther.getPrecision() != lowestPrecisionEnum) { - theOther = new DateTimeType(theOther.getValueAsString()); - theOther.setPrecision(lowestPrecisionEnum); - } - - if (me.hasTimezoneIfRequired() != theOther.hasTimezoneIfRequired()) { - if (me.getPrecision() == theOther.getPrecision()) { - if (me.getPrecision().ordinal() >= TemporalPrecisionEnum.MINUTE.ordinal() && theOther.getPrecision().ordinal() >= TemporalPrecisionEnum.MINUTE.ordinal()) { - boolean couldBeTheSameTime = couldBeTheSameTime(me, theOther) || couldBeTheSameTime(theOther, me); - if (!couldBeTheSameTime) { - return false; - } - } - } - return null; - } - - // Same precision - if (me.getPrecision() == theOther.getPrecision()) { - if (me.getPrecision().ordinal() >= TemporalPrecisionEnum.MINUTE.ordinal()) { - long leftTime = me.getValue().getTime(); - long rightTime = theOther.getValue().getTime(); - return leftTime == rightTime; + if (hasTimezone() != theOther.hasTimezone()) { + if (!couldBeTheSameTime(this, theOther)) { + return false; } else { - String leftTime = me.getValueAsString(); - String rightTime = theOther.getValueAsString(); - return leftTime.equals(rightTime); + return null; } - } - - // Both represent 0 millis but the millis are optional - if (((Integer)0).equals(me.getMillis())) { - if (((Integer)0).equals(theOther.getMillis())) { - if (me.getPrecision().ordinal() >= TemporalPrecisionEnum.SECOND.ordinal()) { - if (theOther.getPrecision().ordinal() >= TemporalPrecisionEnum.SECOND.ordinal()) { - return me.getValue().getTime() == theOther.getValue().getTime(); - } - } + } else { + BaseDateTimeType left = (BaseDateTimeType) this.copy(); + BaseDateTimeType right = (BaseDateTimeType) theOther.copy(); + if (left.hasTimezone() && left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { + left.setTimeZoneZulu(true); } - } - - return false; + if (right.hasTimezone() && right.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { + right.setTimeZoneZulu(true); + } + Integer i = compareTimes(left, right, null); + return i == null ? null : i == 0; + } } - private boolean couldBeTheSameTime(BaseDateTimeType theArg1, BaseDateTimeType theArg2) { - boolean theCouldBeTheSameTime = false; - if (theArg1.getTimeZone() == null && theArg2.getTimeZone() != null) { - long lowLeft = new DateTimeType(theArg1.getValueAsString()+"Z").getValue().getTime() - (14 * DateUtils.MILLIS_PER_HOUR); - long highLeft = new DateTimeType(theArg1.getValueAsString()+"Z").getValue().getTime() + (14 * DateUtils.MILLIS_PER_HOUR); - long right = theArg2.getValue().getTime(); - if (right >= lowLeft && right <= highLeft) { - theCouldBeTheSameTime = true; - } - } - return theCouldBeTheSameTime; + private boolean couldBeTheSameTime(BaseDateTimeType theArg1, BaseDateTimeType theArg2) { + long lowLeft = theArg1.getValue().getTime(); + long highLeft = theArg1.getHighEdge().getValue().getTime(); + if (!theArg1.hasTimezone()) { + lowLeft = lowLeft - (14 * DateUtils.MILLIS_PER_HOUR); + highLeft = highLeft + (14 * DateUtils.MILLIS_PER_HOUR); + } + long lowRight = theArg2.getValue().getTime(); + long highRight = theArg2.getHighEdge().getValue().getTime(); + if (!theArg2.hasTimezone()) { + lowRight = lowRight - (14 * DateUtils.MILLIS_PER_HOUR); + highRight = highRight + (14 * DateUtils.MILLIS_PER_HOUR); + } + if (highRight < lowLeft) { + return false; + } + if (highLeft < lowRight) { + return false; + } + return true; + } + + private BaseDateTimeType getHighEdge() { + BaseDateTimeType result = (BaseDateTimeType) copy(); + switch (getPrecision()) { + case DAY: + result.add(Calendar.DATE, 1); + break; + case MILLI: + break; + case MINUTE: + result.add(Calendar.MINUTE, 1); + break; + case MONTH: + result.add(Calendar.MONTH, 1); + break; + case SECOND: + result.add(Calendar.SECOND, 1); + break; + case YEAR: + result.add(Calendar.YEAR, 1); + break; + default: + break; + } + return result; } boolean hasTimezoneIfRequired() { - return getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal() || - getTimeZone() != null; - } + return getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal() || + getTimeZone() != null; + } - @Override - public String fpValue() { - return "@"+primitiveValue(); - } + boolean hasTimezone() { + return getTimeZone() != null; + } + + public static Integer compareTimes(BaseDateTimeType left, BaseDateTimeType right, Integer def) { + if (left.getYear() < right.getYear()) { + return -1; + } else if (left.getYear() > right.getYear()) { + return 1; + } else if (left.getPrecision() == TemporalPrecisionEnum.YEAR && right.getPrecision() == TemporalPrecisionEnum.YEAR) { + return 0; + } else if (left.getPrecision() == TemporalPrecisionEnum.YEAR || right.getPrecision() == TemporalPrecisionEnum.YEAR) { + return def; + } + + if (left.getMonth() < right.getMonth()) { + return -1; + } else if (left.getMonth() > right.getMonth()) { + return 1; + } else if (left.getPrecision() == TemporalPrecisionEnum.MONTH && right.getPrecision() == TemporalPrecisionEnum.MONTH) { + return 0; + } else if (left.getPrecision() == TemporalPrecisionEnum.MONTH || right.getPrecision() == TemporalPrecisionEnum.MONTH) { + return def; + } + + if (left.getDay() < right.getDay()) { + return -1; + } else if (left.getDay() > right.getDay()) { + return 1; + } else if (left.getPrecision() == TemporalPrecisionEnum.DAY && right.getPrecision() == TemporalPrecisionEnum.DAY) { + return 0; + } else if (left.getPrecision() == TemporalPrecisionEnum.DAY || right.getPrecision() == TemporalPrecisionEnum.DAY) { + return def; + } + + 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; + } + + 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) { + return 0; + } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) { + return def; + } + + if (left.getSecond() < right.getSecond()) { + return -1; + } else if (left.getSecond() > right.getSecond()) { + return 1; + } else if (left.getPrecision() == TemporalPrecisionEnum.SECOND && right.getPrecision() == TemporalPrecisionEnum.SECOND) { + return 0; + } + + if (left.getSecondsMilli() < right.getSecondsMilli()) { + return -1; + } else if (left.getSecondsMilli() > right.getSecondsMilli()) { + return 1; + } else { + return 0; + } + } + + @Override + public String fpValue() { + return "@"+primitiveValue(); + } private TimeZone getTimeZone(String offset) { return timezoneCache.computeIfAbsent(offset, TimeZone::getTimeZone); diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ExpressionNode.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ExpressionNode.java index ab53186f7..792e57a52 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ExpressionNode.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/ExpressionNode.java @@ -46,10 +46,16 @@ public class ExpressionNode { 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, ReplaceMatches, Contains, Replace, Length, + 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, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue, - HasValue, AliasAs, Alias, HtmlChecks, OfType, Type, - ConvertsToBoolean, ConvertsToInteger, ConvertsToString, ConvertsToDecimal, ConvertsToQuantity, ConvertsToDateTime, ConvertsToTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime, ConformsTo; + 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, AliasAs, Alias; public static Function fromCode(String name) { if (name.equals("empty")) return Function.Empty; @@ -87,6 +93,7 @@ public class ExpressionNode { 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; @@ -107,7 +114,16 @@ public class ExpressionNode { if (name.equals("hasValue")) return Function.HasValue; if (name.equals("alias")) return Function.Alias; if (name.equals("aliasAs")) return Function.AliasAs; - if (name.equals("htmlChecks")) return Function.HtmlChecks; + 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("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; @@ -123,8 +139,23 @@ public class ExpressionNode { 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; + return null; } public String toCode() { @@ -164,6 +195,7 @@ public class ExpressionNode { 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"; @@ -184,7 +216,15 @@ public class ExpressionNode { case HasValue : return "hasValue"; case Alias : return "alias"; case AliasAs : return "aliasAs"; - case HtmlChecks : return "htmlChecks"; + 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 OfType : return "ofType"; case Type : return "type"; case ToInteger : return "toInteger"; @@ -200,9 +240,23 @@ public class ExpressionNode { case ConvertsToBoolean : return "convertsToBoolean"; case ConvertsToQuantity : return "convertsToQuantity"; case ConvertsToDateTime : return "convertsToDateTime"; + case ConvertsToDate : return "convertsToDate"; case ConvertsToTime : return "isTime"; case ConformsTo : return "conformsTo"; - default: return "??"; + 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?"; } } } @@ -294,7 +348,7 @@ public class ExpressionNode { case In : return "in"; case Contains : return "contains"; case MemberOf : return "memberOf"; - default: return "??"; + default: return "?custom?"; } } } @@ -446,7 +500,7 @@ public class ExpressionNode { if (!name.startsWith("$")) return true; else - return Utilities.existsInList(name, "$this", "$total"); + return Utilities.existsInList(name, "$this", "$total", "$index"); } public Kind getKind() { @@ -522,7 +576,7 @@ public class ExpressionNode { case Constant: return uniqueId+": "+constant; case Group: return uniqueId+": (Group)"; } - return "??"; + return "?exp-kind?"; } private void write(StringBuilder b) { @@ -568,6 +622,9 @@ public class ExpressionNode { public String check() { + if (kind == null) { + return "Error in expression - node has no kind"; + } switch (kind) { case Name: if (Utilities.noString(name)) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Property.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Property.java index ec404d26f..abb8a229f 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Property.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Property.java @@ -34,6 +34,8 @@ package org.hl7.fhir.r4.model; import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.utilities.MergedList.IMatcher; + /** * A child element or property defined by the FHIR specification * This class is defined as a helper class when iterating the @@ -47,6 +49,16 @@ import java.util.List; */ public class Property { + + public static class PropertyMatcher implements IMatcher { + + @Override + public boolean match(Property l, Property r) { + return l.getName().equals(r.getName()); + } + + } + /** * The name of the property as found in the FHIR specification */ diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TimeType.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TimeType.java index 3891842df..643b777f8 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TimeType.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TimeType.java @@ -1,5 +1,9 @@ package org.hl7.fhir.r4.model; +import org.hl7.fhir.utilities.Utilities; + +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -77,4 +81,71 @@ public class TimeType extends PrimitiveType { return "time"; } + public int getHour() { + String v = getValue(); + if (v.length() < 2) { + return 0; + } + v = v.substring(0, 2); + if (!Utilities.isInteger(v)) { + return 0; + } + return Integer.parseInt(v); + } + + public int getMinute() { + String v = getValue(); + if (v.length() < 5) { + return 0; + } + v = v.substring(3, 5); + if (!Utilities.isInteger(v)) { + return 0; + } + return Integer.parseInt(v); + } + + public float getSecond() { + String v = getValue(); + if (v.length() < 8) { + return 0; + } + v = v.substring(6); + if (!Utilities.isDecimal(v, false, true)) { + return 0; + } + return Float.parseFloat(v); + } + + public TemporalPrecisionEnum getPrecision() { + String v = getValue(); +// if (v.length() == 2) { +// return TemporalPrecisionEnum.HOUR; +// } + if (v.length() == 5) { + return TemporalPrecisionEnum.MINUTE; + } + if (v.length() == 8) { + return TemporalPrecisionEnum.SECOND; + } + if (v.length() > 9) { + return TemporalPrecisionEnum.MILLI; + } + + return null; + } + + public void setPrecision(TemporalPrecisionEnum temp) { + if (temp == TemporalPrecisionEnum.MINUTE) { + setValue(getValue().substring(0, 5)); + } + if (temp == TemporalPrecisionEnum.SECOND) { + setValue(getValue().substring(0, 8)); + } + } + + @Override + public String fpValue() { + return "@T"+primitiveValue(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TypeDetails.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TypeDetails.java index 792cd7c89..b0b06dd4b 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TypeDetails.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/TypeDetails.java @@ -32,6 +32,7 @@ package org.hl7.fhir.r4.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -57,6 +58,7 @@ public class TypeDetails { 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 Set FP_NUMBERS = new HashSet(Arrays.asList(FP_Integer, FP_Decimal)); public static class ProfiledType { private String uri; @@ -207,7 +209,7 @@ public class TypeDetails { if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail)) return true; if (sd.hasBaseDefinition()) { - if (sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Element") && !sd.getType().equals("string") && sd.getType().equals("uri")) + if (sd.getType().equals("uri")) sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string"); else sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); @@ -296,7 +298,7 @@ public class TypeDetails { String t = ProfiledType.ns(n); if (typesContains(t)) return true; - if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { + if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) { t = FP_NS+Utilities.capitalize(n); if (typesContains(t)) return true; diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/test/utils/TestingUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/test/utils/TestingUtilities.java index 8bb22a8c6..0a34c6e9c 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/test/utils/TestingUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/test/utils/TestingUtilities.java @@ -46,17 +46,20 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; import org.fhir.ucum.UcumEssenceService; import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.context.SimpleWorkerContext; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.utilities.CSFile; import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.ToolGlobalSettings; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.ToolsVersion; import org.hl7.fhir.utilities.tests.BaseTestingUtilities; import org.hl7.fhir.utilities.tests.ResourceLoaderTests; +import org.hl7.fhir.utilities.tests.TestConfig; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -476,6 +479,38 @@ public class TestingUtilities { ResourceLoaderTests.copyResourceToFile(TestingUtilities.class, newFilePath, resourcePath); } + public static String loadTestResource(String... paths) throws IOException { + /** + * This 'if' condition checks to see if the fhir-test-cases project (https://github.com/FHIR/fhir-test-cases) is + * installed locally at the same directory level as the core library project is. If so, the test case data is read + * directly from that project, instead of the imported maven dependency jar. It is important, that if you want to + * test against the dependency imported from sonatype nexus, instead of your local copy, you need to either change + * the name of the project directory to something other than 'fhir-test-cases', or move it to another location, not + * at the same directory level as the core project. + */ + String dir = TestConfig.getInstance().getFhirTestCasesDirectory(); + if (dir == null && ToolGlobalSettings.hasTestsPath()) { + dir = ToolGlobalSettings.getTestsPath(); + } + if (dir != null && new CSFile(dir).exists()) { + String n = Utilities.path(dir, Utilities.path(paths)); + // ok, we'll resolve this locally + return TextFile.fileToString(new CSFile(n)); + } else { + // resolve from the package + String contents; + String classpath = ("/org/hl7/fhir/testcases/" + Utilities.pathURL(paths)); + try (InputStream inputStream = BaseTestingUtilities.class.getResourceAsStream(classpath)) { + if (inputStream == null) { + throw new IOException("Can't find file on classpath: " + classpath); + } + contents = IOUtils.toString(inputStream, java.nio.charset.StandardCharsets.UTF_8); + } + return contents; + } + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathConstant.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathConstant.java new file mode 100644 index 000000000..7425d1662 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathConstant.java @@ -0,0 +1,20 @@ +package org.hl7.fhir.r4.utils; + +import org.hl7.fhir.utilities.Utilities; + +public class FHIRPathConstant { + + public static boolean isFHIRPathConstant(String string) { + return !Utilities.noString(string) && ((string.charAt(0) == '\'' || string.charAt(0) == '"') || string.charAt(0) == '@' || string.charAt(0) == '%' || + string.charAt(0) == '-' || string.charAt(0) == '+' || (string.charAt(0) >= '0' && string.charAt(0) <= '9') || + string.equals("true") || string.equals("false") || string.equals("{}")); + } + + public static boolean isFHIRPathFixedName(String string) { + return string != null && (string.charAt(0) == '`'); + } + + public static boolean isFHIRPathStringConstant(String string) { + return string.charAt(0) == '\'' || string.charAt(0) == '"' || string.charAt(0) == '`'; + } +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java index e057967b4..31920faa3 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java @@ -1,8 +1,21 @@ package org.hl7.fhir.r4.utils; -import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import ca.uhn.fhir.util.ElementUtil; -import org.apache.commons.lang3.NotImplementedException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.fhir.ucum.Decimal; import org.fhir.ucum.Pair; import org.fhir.ucum.UcumException; @@ -11,37 +24,67 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.context.IWorkerContext; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; +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.CodeableConcept; +import org.hl7.fhir.r4.model.Constants; +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.Element; +import org.hl7.fhir.r4.model.ElementDefinition; import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r4.model.ExpressionNode.*; +import org.hl7.fhir.r4.model.ExpressionNode; +import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r4.model.ExpressionNode.Function; +import org.hl7.fhir.r4.model.ExpressionNode.Kind; +import org.hl7.fhir.r4.model.ExpressionNode.Operation; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Property; +import org.hl7.fhir.r4.model.Property.PropertyMatcher; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r4.model.TimeType; +import org.hl7.fhir.r4.model.TypeDetails; import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.MergedList; +import org.hl7.fhir.utilities.MergedList.MergeNode; import org.hl7.fhir.utilities.SourceLocation; -import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationOptions; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; -import java.math.BigDecimal; -import java.util.*; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.util.ElementUtil; /* Copyright (c) 2011+, HL7, Inc. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to + * Neither the name of HL7 nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -52,7 +95,7 @@ import java.util.*; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ @@ -62,6 +105,7 @@ import java.util.*; * */ public class FHIRPathEngine { + private enum Equality { Null, True, False } private class FHIRConstant extends Base { @@ -73,7 +117,7 @@ public class FHIRPathEngine { this.value = value; } - @Override + @Override public String fhirType() { return "%constant"; } @@ -94,13 +138,13 @@ public class FHIRPathEngine { public String getValue() { return value; } - + @Override public String primitiveValue() { return value; } } - + private class ClassTypeInfo extends Base { private static final long serialVersionUID = 4909223114071029317L; private Base instance; @@ -127,41 +171,95 @@ public class FHIRPathEngine { @Override public void setIdBase(String value) { } + public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { - if (name.equals("name")) + if (name.equals("name")) { return new Base[]{new StringType(getName())}; - else if (name.equals("namespace")) + } else if (name.equals("namespace")) { return new Base[]{new StringType(getNamespace())}; - else + } else { return super.getProperty(hash, name, checkValid); + } } private String getNamespace() { - if ((instance instanceof Resource)) + if ((instance instanceof Resource)) { return "FHIR"; - else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) + } else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) { return "System"; - else + } else { return "FHIR"; + } } private String getName() { - if ((instance instanceof Resource)) + if ((instance instanceof Resource)) { return instance.fhirType(); - else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) + } else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) { return Utilities.capitalize(instance.fhirType()); - else + } else { return instance.fhirType(); + } } } + public static class TypedElementDefinition { + private ElementDefinition element; + private String type; + private StructureDefinition src; + public TypedElementDefinition(StructureDefinition src, ElementDefinition element, String type) { + super(); + this.element = element; + this.type = type; + this.src = src; + } + public TypedElementDefinition(ElementDefinition element) { + super(); + this.element = element; + } + public ElementDefinition getElement() { + return element; + } + public String getType() { + return type; + } + public List getTypes() { + List res = new ArrayList(); + for (TypeRefComponent tr : element.getType()) { + if (type == null || type.equals(tr.getCode())) { + res.add(tr); + } + } + return res; + } + public boolean hasType(String tn) { + if (type != null) { + return tn.equals(type); + } else { + for (TypeRefComponent t : element.getType()) { + if (tn.equals(t.getCode())) { + return true; + } + } + return false; + } + } + public StructureDefinition getSrc() { + return src; + } + } 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 TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(); + private ValidationOptions terminologyServiceOptions = new ValidationOptions(); + 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 // 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 @@ -201,7 +299,7 @@ public class FHIRPathEngine { */ public List resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException; public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException; - + /** * when the .log() function is called * @@ -218,7 +316,7 @@ public class FHIRPathEngine { * @return null if the function is not known */ public FunctionDetails resolveFunction(String functionName); - + /** * Check the function parameters, and throw an error if they are incorrect, or return the type for the function * @param functionName @@ -226,7 +324,7 @@ public class FHIRPathEngine { * @return */ public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException; - + /** * @param appContext * @param functionName @@ -234,7 +332,7 @@ public class FHIRPathEngine { * @return */ public List executeFunction(Object appContext, List focus, String functionName, List> parameters); - + /** * 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 @@ -242,30 +340,33 @@ public class FHIRPathEngine { * @return * @throws FHIRException */ - public Base resolveReference(Object appContext, String url) throws FHIRException; - + public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException; + public boolean conformsToProfile(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(Object appContext, String url); } - /** * @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)); + } + + public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) { super(); this.worker = worker; - if (this.worker!=null) { - for (StructureDefinition sd : worker.allStructures()) { - if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) - allTypes.put(sd.getName(), sd); - if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { - primitiveTypes.add(sd.getName()); - } + profileUtilities = utilities; + for (StructureDefinition sd : worker.allStructures()) { + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { + allTypes.put(sd.getName(), sd); + } + if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { + primitiveTypes.add(sd.getName()); } } } @@ -284,6 +385,15 @@ public class FHIRPathEngine { this.hostServices = constantResolver; } + public String getLocation() { + 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 @@ -297,17 +407,34 @@ public class FHIRPathEngine { * @param item * @param name * @param result - * @throws FHIRException + * @throws FHIRException */ protected void getChildrenByName(Base item, String name, List result) throws FHIRException { - Base[] list = item.listChildrenByName(name, false); - if (list != null) - for (Base v : list) - if (v != null) - result.add(v); + String tn = null; + if (isAllowPolymorphicNames()) { + // we'll look to see whether we hav a polymorphic name + for (Property p : item.children()) { + if (p.getName().endsWith("[x]")) { + String n = p.getName().substring(0, p.getName().length()-3); + if (name.startsWith(n)) { + tn = name.substring(n.length()); + name = n; + break; + } + } + } + } + Base[] list = item.listChildrenByName(name, false); + if (list != null) { + for (Base v : list) { + if (v != null && (tn == null || v.fhirType().equalsIgnoreCase(tn))) { + result.add(v); + } + } + } } - + public boolean isLegacyMode() { return legacyMode; } @@ -318,6 +445,14 @@ public class FHIRPathEngine { } + public boolean isDoImplicitStringConversion() { + return doImplicitStringConversion; + } + + public void setDoImplicitStringConversion(boolean doImplicitStringConversion) { + this.doImplicitStringConversion = doImplicitStringConversion; + } + // --- public API ------------------------------------------------------- /** * Parse a path for later use using execute @@ -330,14 +465,16 @@ public class FHIRPathEngine { public ExpressionNode parse(String path) throws FHIRLexerException { return parse(path, null); } - + public ExpressionNode parse(String path, String name) throws FHIRLexerException { FHIRLexer lexer = new FHIRLexer(path, name); - if (lexer.done()) + if (lexer.done()) { throw lexer.error("Path cannot be empty"); + } ExpressionNode result = parseExpression(lexer, true); - if (!lexer.done()) + if (!lexer.done()) { throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); + } result.check(); return result; } @@ -356,7 +493,7 @@ public class FHIRPathEngine { public ExpressionNode getNode() { return node; } - + } /** * Parse a path for later use using execute @@ -368,8 +505,9 @@ public class FHIRPathEngine { */ public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { FHIRLexer lexer = new FHIRLexer(path, i); - if (lexer.done()) + if (lexer.done()) { throw lexer.error("Path cannot be empty"); + } ExpressionNode result = parseExpression(lexer, true); result.check(); return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); @@ -402,37 +540,64 @@ public class FHIRPathEngine { */ 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; - if (context == null) { - 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.fetchResource(StructureDefinition.class, context); - types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); - } else { - String ctxt = context.substring(0, context.indexOf('.')); + 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 + } else if (!context.contains(".")) { + StructureDefinition sd = worker.fetchTypeDefinition(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; } - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); - if (sd == null) - throw new PathEngineException("Unknown context "+context, expr.getStart(), expr.toString()); - ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr); - if (ed == null) - throw new PathEngineException("Unknown context element "+context, expr.getStart(), expr.toString()); - 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 { - types = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent t : ed.getDefinition().getType()) - types.addType(t.getCode()); - } - } + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context); + } + ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr); + if (ed == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); + } + 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 { + types = new TypeDetails(CollectionStatus.SINGLETON); + for (TypeRefComponent t : ed.getDefinition().getType()) { + types.addType(t.getCode()); + } + } + } return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true); } + private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) { + String fmt = worker.formatMessage(constName, args); + if (location != null) { + fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); + } + if (holder != null) { + return new PathEngineException(fmt, holder.getStart(), holder.toString()); + } else { + return new PathEngineException(fmt); + } + } + + 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); + } + if (holder != null) { + return new PathEngineException(fmt, holder.getStart(), holder.toString()); + } else { + return new PathEngineException(fmt); + } + } + 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; @@ -440,16 +605,18 @@ public class FHIRPathEngine { types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); } else { ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr); - if (ed == null) - throw new PathEngineException("Unknown context element "+context, expr.getStart(), expr.toString()); - if (ed.fixedType != null) + if (ed == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); + } + if (ed.fixedType != null) { types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); - else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) + } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); - else { + } else { types = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent t : ed.getDefinition().getType()) + for (TypeRefComponent t : ed.getDefinition().getType()) { types.addType(t.getCode()); + } } } @@ -466,30 +633,60 @@ public class FHIRPathEngine { return check(appContext, resourceType, context, parse(expr)); } - private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { - String dateLeftString = theL.primitiveValue(); - DateTimeType dateLeft = new DateTimeType(dateLeftString); - - String dateRightString = theR.primitiveValue(); - DateTimeType dateRight = new DateTimeType(dateRightString); + 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()); if (theEquivalenceTest) { - return dateLeft.equalsUsingFhirPathRules(dateRight) == Boolean.TRUE ? 0 : 1; + return left.equalsUsingFhirPathRules(right) == Boolean.TRUE ? 0 : 1; } - if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { - dateLeft.setTimeZoneZulu(true); + if (left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { + left.setTimeZoneZulu(true); } - if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { - dateRight.setTimeZoneZulu(true); + if (right.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { + right.setTimeZoneZulu(true); } - - dateLeftString = dateLeft.getValueAsString(); - dateRightString = dateRight.getValueAsString(); - - return dateLeftString.compareTo(dateRightString); + return BaseDateTimeType.compareTimes(left, right, null); } + + 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()); + + 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; + } + + 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) { + return 0; + } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) { + return null; + } + + if (left.getSecond() < right.getSecond()) { + return -1; + } else if (left.getSecond() > right.getSecond()) { + return 1; + } else { + return 0; + } + + } + + /** * evaluate a path and return the matching elements * @@ -499,10 +696,11 @@ public class FHIRPathEngine { * @throws FHIRException * @ */ - public List evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { + public List evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { List list = new ArrayList(); - if (base != null) + if (base != null) { list.add(base); + } log = new StringBuilder(); return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); } @@ -513,35 +711,19 @@ 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(Base base, String path) throws FHIRException { + public List evaluate(Base base, String path) throws FHIRException { ExpressionNode exp = parse(path); List list = new ArrayList(); - if (base != null) + if (base != null) { list.add(base); + } log = new StringBuilder(); return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), list, exp, 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, 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, null, base), list, ExpressionNode, true); - } - /** * evaluate a path and return the matching elements * @@ -551,10 +733,11 @@ public class FHIRPathEngine { * @throws FHIRException * @ */ - public List evaluate(Object appContext, Base focusResource, Base 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) + if (base != null) { list.add(base); + } log = new StringBuilder(); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, ExpressionNode, true); } @@ -563,16 +746,35 @@ public class FHIRPathEngine { * 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 + * @param expressionNode - the parsed ExpressionNode 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, 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, null, 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 + * @ + */ + 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) + if (base != null) { list.add(base); + } log = new StringBuilder(); return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true); } @@ -583,10 +785,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)); } @@ -633,7 +835,7 @@ 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 { @@ -654,10 +856,11 @@ public class FHIRPathEngine { StringBuilder b = new StringBuilder(); boolean first = true; for (Base item : items) { - if (first) + if (first) { first = false; - else + } else { b.append(','); + } b.append(convertToString(item)); } @@ -665,16 +868,20 @@ public class FHIRPathEngine { } public String convertToString(Base item) { - if (item.isPrimitive()) + if (item.isPrimitive()) { return item.primitiveValue(); - else if (item instanceof Quantity) { + } 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") + && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) { + return q.getValue().toPlainString()+" "+q.getUnit(); + } if (q.getSystem().equals("http://unitsofmeasure.org")) { String u = "'"+q.getCode()+"'"; return q.getValue().toPlainString()+" "+u; - } - else + } else { return item.toString(); + } } else return item.toString(); } @@ -686,39 +893,43 @@ public class FHIRPathEngine { * @return */ public boolean convertToBoolean(List items) { - if (items == null) + if (items == null) { return false; - else if (items.size() == 1 && items.get(0) instanceof BooleanType) + } else if (items.size() == 1 && items.get(0) instanceof BooleanType) { return ((BooleanType) items.get(0)).getValue(); - else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model + } 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) + if (log.length() > 0) { log.append("; "); + } log.append(name); log.append(": "); boolean first = true; for (Base b : contents) { - if (first) + if (first) { first = false; - else + } else { log.append(","); + } log.append(convertToString(b)); } } } public String forLog() { - if (log.length() > 0) + if (log.length() > 0) { return " ("+log.toString()+")"; - else + } else { return ""; + } } private class ExecutionContext { @@ -729,7 +940,8 @@ public class FHIRPathEngine { private Base thisItem; private List total; private Map aliases; - + private int index; + public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map aliases, Base thisItem) { this.appInfo = appInfo; this.context = context; @@ -737,12 +949,13 @@ public class FHIRPathEngine { this.rootResource = rootResource; this.aliases = aliases; this.thisItem = thisItem; + this.index = 0; } public Base getFocusResource() { return focusResource; } public Base getRootResource() { - return rootResource; + return rootResource; } public Base getThisItem() { return thisItem; @@ -750,18 +963,32 @@ public class FHIRPathEngine { public List getTotal() { return total; } + + public void next() { + index++; + } + public Base getIndex() { + return new IntegerType(index); + } + public void addAlias(String name, List focus) throws FHIRException { - if (aliases == null) + if (aliases == null) { aliases = new HashMap(); - else - aliases = new HashMap(aliases); // clone it, since it's going to change - if (focus.size() > 1) - throw new FHIRException("Attempt to alias a collection, not a singleton"); + } else { + aliases = new HashMap(aliases); // clone it, since it's going to change + } + if (focus.size() > 1) { + throw makeException(null, I18nConstants.FHIRPATH_ALIAS_COLLECTION); + } aliases.put(name, focus.size() == 0 ? null : focus.get(0)); } public Base getAlias(String name) { return aliases == null ? null : aliases.get(name); } + public ExecutionContext setIndex(int i) { + index = i; + return this; + } } private class ExecutionTypeContext { @@ -778,7 +1005,7 @@ public class FHIRPathEngine { this.resource = resource; this.context = context; this.thisItem = thisItem; - + } public String getResource() { return resource; @@ -787,7 +1014,7 @@ public class FHIRPathEngine { return thisItem; } - + } private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { @@ -797,17 +1024,20 @@ public class FHIRPathEngine { 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. // 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. // so we back correct for both +/- and as part of a numeric constant below. if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) { wrapper = new ExpressionNode(lexer.nextId()); wrapper.setKind(Kind.Unary); wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take())); + wrapper.setStart(lexer.getCurrentLocation()); wrapper.setProximal(proximal); } - if (lexer.isConstant()) { + if (lexer.getCurrent() == null) { + throw lexer.error("Expression terminated unexpectedly"); + } 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 @@ -815,6 +1045,7 @@ public class FHIRPathEngine { wrapper.setKind(Kind.Unary); wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1))); wrapper.setProximal(proximal); + wrapper.setStart(lexer.getCurrentLocation()); lexer.setCurrent(lexer.getCurrent().substring(1)); } result.setConstant(processConstant(lexer)); @@ -822,55 +1053,65 @@ public class FHIRPathEngine { 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")) { String s = lexer.take(); - if (s.equals("year") || s.equals("years")) - ucum = "a"; - else if (s.equals("month") || s.equals("months")) - ucum = "mo"; - else if (s.equals("week") || s.equals("weeks")) + unit = s; + if (s.equals("year") || s.equals("years")) { + // this is not the UCUM year + } else if (s.equals("month") || s.equals("months")) { + // this is not the UCUM month + } else if (s.equals("week") || s.equals("weeks")) { ucum = "wk"; - else if (s.equals("day") || s.equals("days")) + } else if (s.equals("day") || s.equals("days")) { ucum = "d"; - else if (s.equals("hour") || s.equals("hours")) + } else if (s.equals("hour") || s.equals("hours")) { ucum = "h"; - else if (s.equals("minute") || s.equals("minutes")) + } else if (s.equals("minute") || s.equals("minutes")) { ucum = "min"; - else if (s.equals("second") || s.equals("seconds")) + } else if (s.equals("second") || s.equals("seconds")) { ucum = "s"; - else // (s.equals("millisecond") || s.equals("milliseconds")) + } else { // (s.equals("millisecond") || s.equals("milliseconds")) ucum = "ms"; - } else + } + } else { ucum = lexer.readConstant("units"); - result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("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())) { lexer.next(); result.setKind(Kind.Group); result.setGroup(parseExpression(lexer, true)); - if (!")".equals(lexer.getCurrent())) + if (!")".equals(lexer.getCurrent())) { throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); + } result.setEnd(lexer.getCurrentLocation()); lexer.next(); } else { - if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) + if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) { throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); - if (lexer.isFixedName()) + } + if (lexer.isFixedName()) { result.setName(lexer.readFixedName("Path Name")); - else + } else { result.setName(lexer.take()); + } result.setEnd(lexer.getCurrentLocation()); - if (!result.checkName()) + if (!result.checkName()) { throw lexer.error("Found "+result.getName()+" expecting a valid token name"); + } if ("(".equals(lexer.getCurrent())) { Function f = Function.fromCode(result.getName()); FunctionDetails details = null; if (f == null) { - if (hostServices != null) + if (hostServices != null) { details = hostServices.resolveFunction(result.getName()); - if (details == null) + } + if (details == null) { throw lexer.error("The name "+result.getName()+" is not a valid function name"); + } f = Function.Custom; } result.setKind(Kind.Function); @@ -878,16 +1119,18 @@ public class FHIRPathEngine { lexer.next(); while (!")".equals(lexer.getCurrent())) { result.getParameters().add(parseExpression(lexer, true)); - if (",".equals(lexer.getCurrent())) + if (",".equals(lexer.getCurrent())) { lexer.next(); - else if (!")".equals(lexer.getCurrent())) + } else if (!")".equals(lexer.getCurrent())) { throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); + } } result.setEnd(lexer.getCurrentLocation()); lexer.next(); checkParameters(lexer, c, result, details); - } else + } else { result.setKind(Kind.Name); + } } ExpressionNode focus = result; if ("[".equals(lexer.getCurrent())) { @@ -896,8 +1139,9 @@ public class FHIRPathEngine { item.setKind(Kind.Function); item.setFunction(ExpressionNode.Function.Item); item.getParameters().add(parseExpression(lexer, true)); - if (!lexer.getCurrent().equals("]")) + if (!lexer.getCurrent().equals("]")) { throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); + } lexer.next(); result.setInner(item); focus = item; @@ -959,8 +1203,9 @@ public class FHIRPathEngine { focus = focus.getOpNext(); } } - if (!work) + if (!work) { return start; + } // entry point: tricky ExpressionNode group; @@ -986,8 +1231,9 @@ public class FHIRPathEngine { // focus points at the group.group do { // run until we find the end of the sequence - while (ops.contains(focus.getOperation())) + while (ops.contains(focus.getOperation())) { focus = focus.getOpNext(); + } if (focus.getOperation() != null) { group.setOperation(focus.getOperation()); group.setOpNext(focus.getOpNext()); @@ -1035,8 +1281,9 @@ public class FHIRPathEngine { return null; } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { return new FHIRConstant(lexer.take()); - } else + } else { throw lexer.error("Invalid Constant "+lexer.getCurrent()); + } } // procedure CheckParamCount(c : integer); @@ -1046,14 +1293,16 @@ public class FHIRPathEngine { // end; private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { - if (exp.getParameters().size() != count) + if (exp.getParameters().size() != count) { throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString()); + } return true; } private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { - if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) + 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()); + } return true; } @@ -1061,7 +1310,7 @@ public class FHIRPathEngine { 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); + 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); @@ -1096,6 +1345,7 @@ public class FHIRPathEngine { 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); @@ -1116,7 +1366,15 @@ public class FHIRPathEngine { case HasValue: return checkParamCount(lexer, location, exp, 0); case Alias: return checkParamCount(lexer, location, exp, 1); case AliasAs: return checkParamCount(lexer, location, exp, 1); - case HtmlChecks: 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 ToInteger: return checkParamCount(lexer, location, exp, 0); case ToDecimal: return checkParamCount(lexer, location, exp, 0); case ToString: return checkParamCount(lexer, location, exp, 0); @@ -1130,86 +1388,108 @@ public class FHIRPathEngine { 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()); } return false; } - private List execute(ExecutionContext context, List focus, ExpressionNode exp, boolean atEntry) throws FHIRException { -// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); + private List execute(ExecutionContext context, List focus, ExpressionNode exp, boolean atEntry) throws FHIRException { + // System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); List work = new ArrayList(); switch (exp.getKind()) { case Unary: work.add(new IntegerType(0)); break; case Name: - if (atEntry && exp.getName().equals("$this")) + if (atEntry && exp.getName().equals("$this")) { work.add(context.getThisItem()); - else if (atEntry && exp.getName().equals("$total")) + } else if (atEntry && exp.getName().equals("$total")) { work.addAll(context.getTotal()); - else + } else if (atEntry && exp.getName().equals("$index")) { + work.add(context.getIndex()); + } else { for (Base item : focus) { List outcome = execute(context, item, exp, atEntry); - for (Base base : outcome) - if (base != null) + for (Base base : outcome) { + if (base != null) { work.add(base); - } + } + } + } + } break; case Function: List work2 = evaluateFunction(context, focus, exp); work.addAll(work2); break; case Constant: - List b = resolveConstant(context, exp.getConstant(), false, exp); - if (b != null) - work.addAll(b); + work.addAll(resolveConstant(context, exp.getConstant(), false, exp)); break; case Group: work2 = execute(context, focus, exp.getGroup(), atEntry); work.addAll(work2); } - if (exp.getInner() != null) + if (exp.getInner() != null) { work = execute(context, work, exp.getInner(), false); + } if (exp.isProximal() && exp.getOperation() != null) { ExpressionNode next = exp.getOpNext(); ExpressionNode last = exp; while (next != null) { List work2 = preOperate(work, last.getOperation(), exp); - if (work2 != null) + if (work2 != null) { work = work2; + } 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) + if (next.getInner() != null) { result.add(new StringType(next.getName()+"."+next.getInner().getName())); - else + } else { result.add(new StringType(next.getName())); + } return result; } private List preOperate(List left, Operation operation, ExpressionNode expr) throws PathEngineException { - if (left.size() == 0) + if (left.size() == 0) { return null; + } switch (operation) { case And: return isBoolean(left, false) ? makeBoolean(false) : null; @@ -1242,25 +1522,30 @@ public class FHIRPathEngine { TypeDetails result = new TypeDetails(null); switch (exp.getKind()) { case Name: - if (atEntry && exp.getName().equals("$this")) + if (atEntry && exp.getName().equals("$this")) { result.update(context.getThisItem()); - else if (atEntry && exp.getName().equals("$total")) + } else if (atEntry && exp.getName().equals("$total")) { result.update(anything(CollectionStatus.UNORDERED)); - else if (atEntry && focus == null) + } else if (atEntry && exp.getName().equals("$index")) { + result.addType(TypeDetails.FP_Integer); + } else if (atEntry && focus == null) { result.update(executeContextType(context, exp.getName(), exp)); - else { + } else { for (String s : focus.getTypes()) { result.update(executeType(s, exp, atEntry)); } - if (result.hasNoTypes()) - throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe(), exp.getStart(), exp.toString()); + if (result.hasNoTypes()) { + throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe()); + } } break; case Function: result.update(evaluateFunctionType(context, focus, exp)); break; case Unary: - result.addType("integer"); + result.addType(TypeDetails.FP_Integer); + result.addType(TypeDetails.FP_Decimal); + result.addType(TypeDetails.FP_Quantity); break; case Constant: result.update(resolveConstantType(context, exp.getConstant(), exp)); @@ -1279,11 +1564,12 @@ public class FHIRPathEngine { ExpressionNode last = exp; while (next != null) { TypeDetails work; - if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) + if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { work = executeTypeName(context, focus, next, atEntry); - else + } else { work = executeType(context, focus, next, atEntry); - result = operateTypes(result, last.getOperation(), work, exp); + } + result = operateTypes(result, last.getOperation(), work, last); last = next; next = next.getOpNext(); } @@ -1293,65 +1579,118 @@ public class FHIRPathEngine { } private List resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr) throws PathEngineException { - if (!(constant instanceof FHIRConstant)) - return new ArrayList<>(Arrays.asList(constant)); + if (constant == null) { + return new ArrayList(); + } + if (!(constant instanceof FHIRConstant)) { + return new ArrayList(Arrays.asList(constant)); + } FHIRConstant c = (FHIRConstant) constant; if (c.getValue().startsWith("%")) { return resolveConstant(context, c.getValue(), beforeContext, expr); } else if (c.getValue().startsWith("@")) { - return new ArrayList<>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1)))); - } else - throw new PathEngineException("Invaild FHIR Constant "+c.getValue(), expr.getStart(), expr.toString()); + return new ArrayList(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); + } else { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, c.getValue()); + } } - private Base processDateConstant(Object appInfo, String value) throws PathEngineException { - if (value.startsWith("T")) - return new TimeType(value.substring(1)).noExtensions(); - String v = value; - if (v.length() > 10) { - int i = v.substring(10).indexOf("-"); - if (i == -1) - i = v.substring(10).indexOf("+"); - if (i == -1) - i = v.substring(10).indexOf("Z"); - v = i == -1 ? value : v.substring(0, 10+i); + private Base processDateConstant(Object appInfo, String value, ExpressionNode expr) throws PathEngineException { + String date = null; + String time = null; + String tz = null; + + TemporalPrecisionEnum temp = null; + + if (value.startsWith("T")) { + time = value.substring(1); + } else if (!value.contains("T")) { + date = value; + } else { + String[] p = value.split("T"); + date = p[0]; + if (p.length > 1) { + time = p[1]; + } + } + + if (time != null) { + int i = time.indexOf("-"); + if (i == -1) { + i = time.indexOf("+"); + } + if (i == -1) { + i = time.indexOf("Z"); + } + if (i > -1) { + tz = time.substring(i); + time = time.substring(0, i); + } + + if (time.length() == 2) { + time = time+":00:00"; + temp = TemporalPrecisionEnum.MINUTE; + } else if (time.length() == 5) { + temp = TemporalPrecisionEnum.MINUTE; + time = time+":00"; + } else if (time.contains(".")) { + temp = TemporalPrecisionEnum.MILLI; + } else { + temp = TemporalPrecisionEnum.SECOND; + } + } + + + if (date == null) { + if (tz != null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, value); + } else { + TimeType tt = new TimeType(time); + tt.setPrecision(temp); + return tt.noExtensions(); + } + } else if (time != null) { + DateTimeType dt = new DateTimeType(date+"T"+time+(tz == null ? "" : tz)); + dt.setPrecision(temp); + return dt.noExtensions(); + } else { + return new DateType(date).noExtensions(); } - if (v.length() > 10) - return new DateTimeType(value).noExtensions(); - else - return new DateType(value).noExtensions(); } private List resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr) throws PathEngineException { - if (s.equals("%sct")) - return new ArrayList<>(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions())); - else if (s.equals("%loinc")) - return new ArrayList<>(Arrays.asList(new StringType("http://loinc.org").noExtensions())); - else if (s.equals("%ucum")) - return new ArrayList<>(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions())); - else if (s.equals("%resource")) { - if (context.focusResource == null) - throw new PathEngineException("Cannot use %resource in this context", expr.getStart(), expr.toString()); - return new ArrayList<>(Arrays.asList(context.focusResource)); + if (s.equals("%sct")) { + return new ArrayList(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions())); + } else if (s.equals("%loinc")) { + return new ArrayList(Arrays.asList(new StringType("http://loinc.org").noExtensions())); + } else if (s.equals("%ucum")) { + return new ArrayList(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions())); + } else if (s.equals("%resource")) { + if (context.focusResource == null) { + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource"); + } + return new ArrayList(Arrays.asList(context.focusResource)); } else if (s.equals("%rootResource")) { - if (context.rootResource == null) - throw new PathEngineException("Cannot use %rootResource in this context", expr.getStart(), expr.toString()); - return new ArrayList<>(Arrays.asList(context.rootResource)); + if (context.rootResource == null) { + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource"); + } + return new ArrayList(Arrays.asList(context.rootResource)); } else if (s.equals("%context")) { - return new ArrayList<>(Arrays.asList(context.context)); - } 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())); - else if (s.startsWith("%`cs-")) - 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())); - else if (hostServices == null) - throw new PathEngineException("Unknown fixed constant '"+s+"'", expr.getStart(), expr.toString()); - else + return new ArrayList(Arrays.asList(context.context)); + } 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())); + } else if (s.startsWith("%`cs-")) { + 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())); + } else if (hostServices == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); + } else { return hostServices.resolveConstant(context.appInfo, s.substring(1), beforeContext); + } } @@ -1409,33 +1748,33 @@ public class FHIRPathEngine { } - private List operate(ExecutionContext context, List left, Operation operation, List right, ExpressionNode expr) 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); - case Equivalent: return opEquivalent(left, right, expr); - case NotEquals: return opNotEquals(left, right, expr); - case NotEquivalent: return opNotEquivalent(left, right, expr); - case LessThan: return opLessThan(left, right, expr); - case Greater: return opGreater(left, right, expr); - case LessOrEqual: return opLessOrEqual(left, right, expr); - case GreaterOrEqual: return opGreaterOrEqual(left, right, expr); - case Union: return opUnion(left, right, expr); - case In: return opIn(left, right, expr); - case MemberOf: return opMemberOf(context, left, right, expr); - case Contains: return opContains(left, right, expr); - case Or: return opOr(left, right, expr); - case And: return opAnd(left, right, expr); - case Xor: return opXor(left, right, expr); - case Implies: return opImplies(left, right, expr); - case Plus: return opPlus(left, right, expr); - case Times: return opTimes(left, right, expr); - case Minus: return opMinus(left, right, expr); - case Concatenate: return opConcatenate(left, right, expr); - case DivideBy: return opDivideBy(left, right, expr); - case Div: return opDiv(left, right, expr); - case Mod: return opMod(left, right, expr); - case Is: return opIs(left, right, expr); - case As: return opAs(left, right, expr); + 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()); } @@ -1443,13 +1782,14 @@ public class FHIRPathEngine { private List opAs(List left, List right, ExpressionNode expr) { List result = new ArrayList<>(); - if (right.size() != 1) + if (right.size() != 1) { return result; - else { + } else { String tn = convertToString(right); for (Base nextLeft : left) { - if (tn.equals(nextLeft.fhirType())) + if (tn.equals(nextLeft.fhirType())) { result.add(nextLeft); + } } } return result; @@ -1458,16 +1798,29 @@ public class FHIRPathEngine { private List opIs(List left, List right, ExpressionNode expr) { List result = new ArrayList(); - if (left.size() != 1 || right.size() != 1) + if (left.size() == 0 || right.size() == 0) { + } 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) + 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()) + } 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()); - else - result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); + } else { + if (left.get(0).fhirType().equals(tn)) { + result.add(new BooleanType(true).noExtensions()); + } else { + StructureDefinition sd = worker.fetchTypeDefinition(left.get(0).fhirType()); + while (sd != null) { + if (tn.equals(sd.getType())) { + return makeBoolean(true); + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return makeBoolean(false); + } + } } return result; } @@ -1492,44 +1845,48 @@ public class FHIRPathEngine { case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Times: TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); - if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) + 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")) + } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { result.addType(TypeDetails.FP_Decimal); + } return result; case DivideBy: result = new TypeDetails(CollectionStatus.SINGLETON); - if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) + if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { result.addType(TypeDetails.FP_Decimal); - else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) + } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { result.addType(TypeDetails.FP_Decimal); + } return result; case Concatenate: result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); return result; case Plus: result = new TypeDetails(CollectionStatus.SINGLETON); - if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) + 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")) + } 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")) { + } 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.getStart(), expr.toString()); + throw new PathEngineException(String.format("Error in date arithmetic: Unable to add type {0} to {1}", right.getType(), left.getType()), expr.getOpStart(), expr.toString()); } } return result; case Minus: result = new TypeDetails(CollectionStatus.SINGLETON); - if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) + 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")) + } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { result.addType(TypeDetails.FP_Decimal); - else if (left.hasType(worker, "date", "dateTime", "instant")) { + } else if (left.hasType(worker, "Quantity") && right.hasType(worker, "Quantity")) { + result.addType(TypeDetails.FP_Quantity); + } else if (left.hasType(worker, "date", "dateTime", "instant")) { if (right.hasType(worker, "Quantity")) { result.addType(left.getType()); } else { @@ -1540,10 +1897,11 @@ public class FHIRPathEngine { case Div: case Mod: result = new TypeDetails(CollectionStatus.SINGLETON); - if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) + 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")) + } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 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); @@ -1554,56 +1912,62 @@ public class FHIRPathEngine { } - private List opEquals(List left, List right) { - if (left.size() == 0 || right.size() == 0) + private List opEquals(List left, List right, ExpressionNode expr) { + if (left.size() == 0 || right.size() == 0) { return new ArrayList(); + } - if (left.size() != right.size()) + if (left.size() != right.size()) { return makeBoolean(false); + } boolean res = true; boolean nil = false; for (int i = 0; i < left.size(); i++) { Boolean eq = doEquals(left.get(i), right.get(i)); - if (eq == null) + if (eq == null) { nil = true; - else if (eq == false) { + } else if (eq == false) { res = false; break; } } - if (!res) + if (!res) { return makeBoolean(res); - else if (nil) + } else if (nil) { return new ArrayList(); - else + } else { return makeBoolean(res); + } } private List opNotEquals(List left, List right, ExpressionNode expr) { - if (!legacyMode && (left.size() == 0 || right.size() == 0)) + if (!legacyMode && (left.size() == 0 || right.size() == 0)) { return new ArrayList(); + } - if (left.size() != right.size()) + if (left.size() != right.size()) { return makeBoolean(true); + } boolean res = true; boolean nil = false; for (int i = 0; i < left.size(); i++) { Boolean eq = doEquals(left.get(i), right.get(i)); - if (eq == null) + if (eq == null) { nil = true; - else if (eq == true) { + } else if (eq == true) { res = false; break; } } - if (!res) + if (!res) { return makeBoolean(res); - else if (nil) + } else if (nil) { return new ArrayList(); - else + } else { return makeBoolean(res); + } } private String removeTrailingZeros(String s) { @@ -1616,11 +1980,11 @@ public class FHIRPathEngine { if (s.charAt(i) == '.') { i--; dot = true; - } - else if (!dot && s.charAt(i) == '0') + } else if (!dot && s.charAt(i) == '0') { i--; - else + } else { done = true; + } } return s.substring(0, i+1); } @@ -1630,57 +1994,128 @@ public class FHIRPathEngine { right = removeTrailingZeros(right); return left.equals(right); } - - private Boolean compareDates(BaseDateTimeType left, BaseDateTimeType right) { + + private Boolean datesEqual(BaseDateTimeType left, BaseDateTimeType right) { return left.equalsUsingFhirPathRules(right); } - + private Boolean doEquals(Base left, Base right) { - if (left instanceof Quantity && right instanceof Quantity) + if (left instanceof Quantity && right instanceof Quantity) { return qtyEqual((Quantity) left, (Quantity) right); - else if (left.isDateTime() && right.isDateTime()) { - return compareDates(left.dateTimeValue(), right.dateTimeValue()); - } else if (left instanceof DecimalType || right instanceof DecimalType) + } else if (left.isDateTime() && right.isDateTime()) { + return datesEqual(left.dateTimeValue(), right.dateTimeValue()); + } 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()); - else + } else if (left.isPrimitive() && right.isPrimitive()) { + return Base.equals(left.primitiveValue(), right.primitiveValue()); + } else { return Base.compareDeep(left, right, false); - } - - - private boolean doEquivalent(Base left, Base right, ExpressionNode expr) throws PathEngineException { - if (left instanceof Quantity && right instanceof Quantity) - return qtyEquivalent((Quantity) left, (Quantity) right, expr); - if (left.hasType("integer") && right.hasType("integer")) - return doEquals(left, right); - if (left.hasType("boolean") && right.hasType("boolean")) - return doEquals(left, right); - 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")) - return compareDateTimeElements(left, right, true) == 0; - if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) - return Utilities.equivalent(convertToString(left), convertToString(right)); - - throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()), expr.getStart(), expr.toString()); - } - - private boolean qtyEqual(Quantity left, Quantity right) { - if (worker.getUcumService() != null) { - DecimalType dl = qtyToCanonical(left); - DecimalType dr = qtyToCanonical(right); - if (dl != null && dr != null) - return doEquals(dl, dr); } - return left.equals(right); } - private DecimalType qtyToCanonical(Quantity q) { - if (!"http://unitsofmeasure.org".equals(q.getSystem())) + private boolean doEquivalent(Base left, Base right) throws PathEngineException { + if (left instanceof Quantity && right instanceof Quantity) { + return qtyEquivalent((Quantity) left, (Quantity) right); + } + if (left.hasType("integer") && right.hasType("integer")) { + return doEquals(left, right); + } + if (left.hasType("boolean") && right.hasType("boolean")) { + return doEquals(left, right); + } + 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")) { + Integer i = compareDateTimeElements(left, right, true); + if (i == null) { + i = 0; + } + return i == 0; + } + if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) { + return Utilities.equivalent(convertToString(left), convertToString(right)); + } + if (left.isPrimitive() && right.isPrimitive()) { + return Utilities.equivalent(left.primitiveValue(), right.primitiveValue()); + } + if (!left.isPrimitive() && !right.isPrimitive()) { + MergedList props = new MergedList(left.children(), right.children(), new PropertyMatcher()); + for (MergeNode t : props) { + if (t.hasLeft() && t.hasRight()) { + if (t.getLeft().hasValues() && t.getRight().hasValues()) { + MergedList values = new MergedList(t.getLeft().getValues(), t.getRight().getValues()); + for (MergeNode v : values) { + if (v.hasLeft() && v.hasRight()) { + if (!doEquivalent(v.getLeft(), v.getRight())) { + return false; + } + } else if (v.hasLeft() || v.hasRight()) { + return false; + } + } + } else if (t.getLeft().hasValues() || t.getRight().hasValues()) { + return false; + } + } else { + return false; + } + } + return true; + } else { + return false; + } + } + + private Boolean qtyEqual(Quantity left, Quantity right) { + if (!left.hasValue() && !right.hasValue()) { + return true; + } + if (!left.hasValue() || !right.hasValue()) { return null; + } + if (worker.getUcumService() != null) { + Pair dl = qtyToCanonicalPair(left); + 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())); + } else { + return false; + } + } + } + if (left.hasCode() || right.hasCode()) { + if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { + return null; + } + } else if (!left.hasUnit() || right.hasUnit()) { + if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { + return null; + } + } + return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue())); + } + + private Pair qtyToCanonicalPair(Quantity q) { + if (!"http://unitsofmeasure.org".equals(q.getSystem())) { + return null; + } try { - Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); + Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode()); + Pair c = worker.getUcumService().getCanonicalForm(p); + return c; + } catch (UcumException e) { + return null; + } + } + + private DecimalType qtyToCanonicalDecimal(Quantity q) { + if (!"http://unitsofmeasure.org".equals(q.getSystem())) { + return null; + } + try { + Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode()); Pair c = worker.getUcumService().getCanonicalForm(p); return new DecimalType(c.getValue().asDecimal()); } catch (UcumException e) { @@ -1694,8 +2129,9 @@ public class FHIRPathEngine { private Pair qtyToPair(Quantity q) { - if (!"http://unitsofmeasure.org".equals(q.getSystem())) + if (!"http://unitsofmeasure.org".equals(q.getSystem())) { return null; + } try { return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); } catch (UcumException e) { @@ -1704,27 +2140,48 @@ public class FHIRPathEngine { } - private boolean qtyEquivalent(Quantity left, Quantity right, ExpressionNode expr) throws PathEngineException { - if (worker.getUcumService() != null) { - DecimalType dl = qtyToCanonical(left); - DecimalType dr = qtyToCanonical(right); - if (dl != null && dr != null) - return doEquivalent(dl, dr, expr); + private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { + if (!left.hasValue() && !right.hasValue()) { + return true; } - return left.equals(right); + if (!left.hasValue() || !right.hasValue()) { + return null; + } + if (worker.getUcumService() != null) { + Pair dl = qtyToCanonicalPair(left); + 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())); + } else { + return false; + } + } + } + if (left.hasCode() || right.hasCode()) { + if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { + return null; + } + } else if (!left.hasUnit() || right.hasUnit()) { + if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { + return null; + } + } + 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()) + if (left.size() != right.size()) { return makeBoolean(false); + } boolean res = true; for (int i = 0; i < left.size(); i++) { boolean found = false; for (int j = 0; j < right.size(); j++) { - if (doEquivalent(left.get(i), right.get(j), expr)) { + if (doEquivalent(left.get(i), right.get(j))) { found = true; break; } @@ -1738,14 +2195,15 @@ public class FHIRPathEngine { } private List opNotEquivalent(List left, List right, ExpressionNode expr) throws PathEngineException { - if (left.size() != right.size()) + if (left.size() != right.size()) { return makeBoolean(true); + } boolean res = true; for (int i = 0; i < left.size(); i++) { boolean found = false; for (int j = 0; j < right.size(); j++) { - if (doEquivalent(left.get(i), right.get(j), expr)) { + if (doEquivalent(left.get(i), right.get(j))) { found = true; break; } @@ -1760,36 +2218,47 @@ public class FHIRPathEngine { private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url"}; - private List opLessThan(List left, List right, ExpressionNode expr) throws FHIRException { + private List opLessThan(List left, List right, ExpressionNode expr) throws FHIRException { 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"))) - return makeBoolean(compareDateTimeElements(l, r, false) < 0); - else if ((l.hasType("time")) && (r.hasType("time"))) - return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); - else - throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType(), expr.getStart(), expr.toString()); + } 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); + } + } else if ((l.hasType("time")) && (r.hasType("time"))) { + Integer i = compareTimeElements(l, r, false); + if (i == null) { + return makeNull(); + } else { + return makeBoolean(i < 0); + } + } 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") ) { List lUnit = left.get(0).listChildrenByName("code"); List rUnit = right.get(0).listChildrenByName("code"); if (Base.compareDeep(lUnit, rUnit, true)) { return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); } else { - if (worker.getUcumService() == null) + if (worker.getUcumService() == null) { return makeBoolean(false); - else { + } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opLessThan(dl, dr, expr); } } @@ -1797,35 +2266,46 @@ public class FHIRPathEngine { return new ArrayList(); } - private List opGreater(List left, List right, ExpressionNode expr) throws FHIRException { + private List opGreater(List left, List right, ExpressionNode expr) throws FHIRException { 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"))) - return makeBoolean(compareDateTimeElements(l, r, false) > 0); - else if ((l.hasType("time")) && (r.hasType("time"))) - return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); - else - throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType(), expr.getStart(), expr.toString()); + } 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); + } + } else if ((l.hasType("time")) && (r.hasType("time"))) { + Integer i = compareTimeElements(l, r, false); + if (i == null) { + return makeNull(); + } else { + return makeBoolean(i > 0); + } + } 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") ) { List lUnit = left.get(0).listChildrenByName("unit"); List rUnit = right.get(0).listChildrenByName("unit"); if (Base.compareDeep(lUnit, rUnit, true)) { return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); } else { - if (worker.getUcumService() == null) + if (worker.getUcumService() == null) { return makeBoolean(false); - else { + } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opGreater(dl, dr, expr); } } @@ -1833,22 +2313,34 @@ public class FHIRPathEngine { return new ArrayList(); } - private List opLessOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) + private List opLessOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { + 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"))) - return makeBoolean(compareDateTimeElements(l, r, false) <= 0); - else if ((l.hasType("time")) && (r.hasType("time"))) - return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); - else - throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType(), expr.getStart(), expr.toString()); + } 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); + } + } else if ((l.hasType("time")) && (r.hasType("time"))) { + Integer i = compareTimeElements(l, r, false); + if (i == null) { + return makeNull(); + } else { + return makeBoolean(i <= 0); + } + } 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") ) { List lUnits = left.get(0).listChildrenByName("unit"); String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; @@ -1857,13 +2349,13 @@ public class FHIRPathEngine { if ((lunit == null && runit == null) || lunit.equals(runit)) { return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); } else { - if (worker.getUcumService() == null) + if (worker.getUcumService() == null) { return makeBoolean(false); - else { + } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opLessOrEqual(dl, dr, expr); } } @@ -1871,35 +2363,47 @@ public class FHIRPathEngine { return new ArrayList(); } - private List opGreaterOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { - if (left.size() == 0 || right.size() == 0) + private List opGreaterOrEqual(List left, List right, ExpressionNode expr) throws FHIRException { + 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"))) - return makeBoolean(compareDateTimeElements(l, r, false) >= 0); - else if ((l.hasType("time")) && (r.hasType("time"))) - return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); - else - throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType(), expr.getStart(), expr.toString()); + } 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); + } + } else if ((l.hasType("time")) && (r.hasType("time"))) { + Integer i = compareTimeElements(l, r, false); + if (i == null) { + return makeNull(); + } else { + return makeBoolean(i >= 0); + } + } 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") ) { 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); } else { - if (worker.getUcumService() == null) + if (worker.getUcumService() == null) { return makeBoolean(false); - else { + } else { List dl = new ArrayList(); - dl.add(qtyToCanonical((Quantity) left.get(0))); + dl.add(qtyToCanonicalDecimal((Quantity) left.get(0))); List dr = new ArrayList(); - dr.add(qtyToCanonical((Quantity) right.get(0))); + dr.add(qtyToCanonicalDecimal((Quantity) right.get(0))); return opGreaterOrEqual(dl, dr, expr); } } @@ -1907,31 +2411,42 @@ public class FHIRPathEngine { return new ArrayList(); } - private List opMemberOf(ExecutionContext context, List left, List right, ExpressionNode expr) throws FHIRException { - boolean ans = false; - ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, right.get(0).primitiveValue()) : worker.fetchResource(ValueSet.class, right.get(0).primitiveValue()); - if (vs != null) { - for (Base l : left) { - if (l.fhirType().equals("code")) { - if (worker.validateCode(terminologyServiceOptions , l.castToCoding(l), vs).isOk()) + 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(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.guessSystem() , l.castToCoding(l), vs).isOk()) { ans = true; - } else if (l.fhirType().equals("Coding")) { - if (worker.validateCode(terminologyServiceOptions, l.castToCoding(l), vs).isOk()) - ans = true; - } else if (l.fhirType().equals("CodeableConcept")) { - if (worker.validateCode(terminologyServiceOptions, l.castToCodeableConcept(l), vs).isOk()) - ans = true; - } - } - } - return makeBoolean(ans); - } + } + } else if (l.fhirType().equals("Coding")) { + if (worker.validateCode(terminologyServiceOptions, l.castToCoding(l), vs).isOk()) { + ans = true; + } + } 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()); + if (vr.isOk()) { + ans = true; + } + } else { + // System.out.println("unknown type in opMemberOf: "+l.fhirType()); + } + } + } + return makeBoolean(ans); + } 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; for (Base l : left) { boolean f = false; @@ -1951,8 +2466,9 @@ public class FHIRPathEngine { } private List opContains(List left, List right, ExpressionNode expr) { - if (left.size() == 0 || right.size() == 0) - return new ArrayList(); + if (left.size() == 0 || right.size() == 0) { + return new ArrayList(); + } boolean ans = true; for (Base r : right) { boolean f = false; @@ -1972,36 +2488,42 @@ 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) - throw new PathEngineException("Error performing +: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive()) - throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing +: right operand has more than one value", expr.getStart(), expr.toString()); - 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"))) - throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "+"); + } + if (!left.get(0).isPrimitive()) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "+", left.get(0).fhirType()); + } + 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"))) { + 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")) + } else if (l.isDateTime() && r.hasType("Quantity")) { result.add(dateAdd((BaseDateTimeType) l, (Quantity) r, false, expr)); - else - throw new PathEngineException(String.format("Error performing +: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); + } else { + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType()); + } return result; } - private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode expr) { + private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) { BaseDateTimeType result = (BaseDateTimeType) d.copy(); - + int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue(); switch (q.hasCode() ? q.getCode() : q.getUnit()) { case "years": @@ -2009,13 +2531,13 @@ public class FHIRPathEngine { 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()), expr.getStart(), expr.toString()); + 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": 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()), expr.getStart(), expr.toString()); + 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": case "wk": @@ -2047,33 +2569,37 @@ public class FHIRPathEngine { result.add(Calendar.MILLISECOND, value); break; default: - throw new PathEngineException(String.format("Error in date arithmetic: unrecognized time unit %s", q.getCode()), expr.getStart(), expr.toString()); + throw new PathEngineException(String.format("Error in date arithmetic: unrecognized time unit %s", q.getCode())); } return result; } - private List opTimes(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) - throw new PathEngineException("Error performing *: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing *: right operand has more than one value", expr.getStart(), expr.toString()); - if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "*"); + } + if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType()); + } + if (right.size() > 1) { + throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "*"); + } + if (!right.get(0).isPrimitive() && !(right.get(0) instanceof 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("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) { + } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { Pair pl = qtyToPair((Quantity) l); Pair pr = qtyToPair((Quantity) r); Pair p; @@ -2083,21 +2609,26 @@ public class FHIRPathEngine { } catch (UcumException e) { throw new PathEngineException(e.getMessage(), expr.getOpStart(), expr.toString(), e); } - } else - throw new PathEngineException(String.format("Error performing *: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); + } else { + 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 new PathEngineException("Error performing &: left operand has more than one value", expr.getStart(), expr.toString()); - if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) - throw new PathEngineException(String.format("Error performing &: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing &: right operand has more than one value", expr.getStart(), expr.toString()); - if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) - throw new PathEngineException(String.format("Error performing &: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&"); + } + if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType()); + } + if (right.size() > 1) { + throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "&"); + } + if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) { + throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType()); + } List result = new ArrayList(); String l = left.size() == 0 ? "" : left.get(0).primitiveValue(); @@ -2109,12 +2640,14 @@ public class FHIRPathEngine { private List opUnion(List left, List right, ExpressionNode expr) { List result = new ArrayList(); for (Base item : left) { - if (!doContains(result, item)) + if (!doContains(result, item)) { result.add(item); + } } for (Base item : right) { - if (!doContains(result, item)) + if (!doContains(result, item)) { result.add(item); + } } return result; } @@ -2122,8 +2655,9 @@ public class FHIRPathEngine { private boolean doContains(List list, Base item) { for (Base test : list) { Boolean eq = doEquals(test, item); - if (eq != null && eq == true) + if (eq != null && eq == true) { return true; + } } return false; } @@ -2135,10 +2669,11 @@ public class FHIRPathEngine { switch (l) { case False: return makeBoolean(false); case Null: - if (r == Equality.False) + if (r == Equality.False) { return makeBoolean(false); - else + } else { return makeNull(); + } case True: switch (r) { case False: return makeBoolean(false); @@ -2159,10 +2694,11 @@ public class FHIRPathEngine { switch (l) { case True: return makeBoolean(true); case Null: - if (r == Equality.True) + if (r == Equality.True) { return makeBoolean(true); - else + } else { return makeNull(); + } case False: switch (r) { case False: return makeBoolean(false); @@ -2197,11 +2733,11 @@ 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) + } else if (right.size() == 0) { return makeNull(); - else switch (asBool(right, expr)) { + } else switch (asBool(right, expr)) { case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); case Null: return makeNull(); case True: return makeBoolean(true); @@ -2211,43 +2747,60 @@ public class FHIRPathEngine { 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) - throw new PathEngineException("Error performing -: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive()) - throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing -: right operand has more than one value", expr.getStart(), expr.toString()); - 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"))) - throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "-"); + } + if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType()); + } + 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"))) { + 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("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.isDateTime() && 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 - throw new PathEngineException(String.format("Error performing -: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); + } else { + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType()); + } return result; } private List opDivideBy(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) - throw new PathEngineException("Error performing /: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing /: right operand has more than one value"); - if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "/"); + } + if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType()); + } + if (right.size() > 1) { + throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "/"); + } + if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) { + throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType()); + } List result = new ArrayList(); Base l = left.get(0); @@ -2260,75 +2813,91 @@ public class FHIRPathEngine { Decimal d2 = new Decimal(r.primitiveValue()); result.add(new DecimalType(d1.divide(d2).asDecimal())); } catch (UcumException e) { - throw new PathEngineException(e); + // just return nothing } } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { Pair pl = qtyToPair((Quantity) l); Pair pr = qtyToPair((Quantity) r); Pair p; try { - p = worker.getUcumService().multiply(pl, pr); + p = worker.getUcumService().divideBy(pl, pr); result.add(pairToQty(p)); } catch (UcumException e) { - throw new PathEngineException(e.getMessage(), expr.getOpStart(), expr.toString(), e); + // just return nothing } - } else - throw new PathEngineException(String.format("Error performing /: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); + } else { + 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) - throw new PathEngineException("Error performing div: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing div: right operand has more than one value", expr.getStart(), expr.toString()); - if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) - throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } + if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "div"); + } + if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType()); + } + if (right.size() > 1) { + throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "div"); + } + if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) { + throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType()); + } List result = new ArrayList(); Base l = left.get(0); Base r = right.get(0); - 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")) { + if (l.hasType("integer") && r.hasType("integer")) { + int divisor = Integer.parseInt(r.primitiveValue()); + if (divisor != 0) { + result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor)); + } + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { Decimal d1; try { d1 = new Decimal(l.primitiveValue()); Decimal d2 = new Decimal(r.primitiveValue()); result.add(new IntegerType(d1.divInt(d2).asDecimal())); } catch (UcumException e) { - throw new PathEngineException(e); + // just return nothing } + } else { + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType()); } - else - throw new PathEngineException(String.format("Error performing div: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); return result; } private List opMod(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) - throw new PathEngineException("Error performing mod: left operand has more than one value", expr.getStart(), expr.toString()); - if (!left.get(0).isPrimitive()) - throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType()), expr.getStart(), expr.toString()); - if (right.size() > 1) - throw new PathEngineException("Error performing mod: right operand has more than one value", expr.getStart(), expr.toString()); - if (!right.get(0).isPrimitive()) - throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType()), expr.getStart(), expr.toString()); + } if (left.size() > 1) { + throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod"); + } + if (!left.get(0).isPrimitive()) { + throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType()); + } + if (right.size() > 1) { + throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "mod"); + } + if (!right.get(0).isPrimitive()) { + throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType()); + } List result = new ArrayList(); Base l = left.get(0); Base r = right.get(0); - 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")) { + 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)); + } + } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { Decimal d1; try { d1 = new Decimal(l.primitiveValue()); @@ -2337,101 +2906,126 @@ public class FHIRPathEngine { } catch (UcumException e) { throw new PathEngineException(e); } + } else { + throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType()); } - else - throw new PathEngineException(String.format("Error performing mod: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()), expr.getStart(), expr.toString()); return result; } private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr) throws PathEngineException { - if (constant instanceof BooleanType) + if (constant instanceof BooleanType) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - else if (constant instanceof IntegerType) + } else if (constant instanceof IntegerType) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); - else if (constant instanceof DecimalType) + } else if (constant instanceof DecimalType) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); - else if (constant instanceof Quantity) + } else if (constant instanceof Quantity) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); - else if (constant instanceof FHIRConstant) + } else if (constant instanceof FHIRConstant) { return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr); - else + } else if (constant == null) { + return new TypeDetails(CollectionStatus.SINGLETON); + } else { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + } } private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr) throws PathEngineException { if (s.startsWith("@")) { - if (s.startsWith("@T")) + if (s.startsWith("@T")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); - else + } else { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); - } else if (s.equals("%sct")) + } + } else if (s.equals("%sct")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.equals("%loinc")) + } else if (s.equals("%loinc")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.equals("%ucum")) + } else if (s.equals("%ucum")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.equals("%resource")) { - if (context.resource == null) - throw new PathEngineException("%resource cannot be used in this context", expr.getStart(), expr.toString()); + } else if (s.equals("%resource")) { + if (context.resource == null) { + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource"); + } return new TypeDetails(CollectionStatus.SINGLETON, context.resource); } else if (s.equals("%rootResource")) { - if (context.resource == null) - throw new PathEngineException("%rootResource cannot be used in this context", expr.getStart(), expr.toString()); + if (context.resource == null) { + throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource"); + } return new TypeDetails(CollectionStatus.SINGLETON, context.resource); } else if (s.equals("%context")) { return context.context; - } else if (s.equals("%map-codes")) + } else if (s.equals("%map-codes")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.equals("%us-zip")) + } else if (s.equals("%us-zip")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.startsWith("%`vs-")) + } else if (s.startsWith("%`vs-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.startsWith("%`cs-")) + } else if (s.startsWith("%`cs-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (s.startsWith("%`ext-")) + } else if (s.startsWith("%`ext-")) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); - else if (hostServices == null) - throw new PathEngineException("Unknown fixed constant type for '"+s+"'", expr.getStart(), expr.toString()); - else + } else if (hostServices == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); + } else { return hostServices.resolveConstantType(context.appInfo, s); + } } - private List execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { + 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(context.appInfo, exp.getName(), true); - if (temp != null) { + if (!temp.isEmpty()) { result.addAll(temp); return result; } } - if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up - if (item.isResource() && item.fhirType().equals(exp.getName())) - result.add(item); - } else + 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); + } + } else { + while (sd != null) { + if (sd.getType().equals(exp.getName())) { + result.add(item); + break; + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + } + } 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... - List temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false); - if (temp != null) { - result.addAll(temp); - } + result.addAll(hostServices.resolveConstant(context.appInfo, exp.getName(), false)); } return result; } + private String getParent(String rn) { + return null; + } + + private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr) throws PathEngineException, DefinitionException { - if (hostServices == null) - throw new PathEngineException("Unable to resolve context reference since no host services are provided", expr.getStart(), expr.toString()); + if (hostServices == null) { + throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference"); + } return hostServices.resolveConstantType(context.appInfo, name); } - + 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 + if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up return new TypeDetails(CollectionStatus.SINGLETON, type); + } TypeDetails result = new TypeDetails(null); getChildTypesByName(type, exp.getName(), result, exp); return result; @@ -2446,22 +3040,28 @@ public class FHIRPathEngine { @SuppressWarnings("unchecked") private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) 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) { paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - else + } else { + int i = 0; for (ExpressionNode expr : exp.getParameters()) { - if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) + if (isExpressionParameter(exp, i)) { paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); - else - paramTypes.add(executeType(context, focus, expr, true)); + } else { + paramTypes.add(executeType(context, context.thisItem, expr, true)); + } + i++; } + } switch (exp.getFunction()) { case Empty : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case Not : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case Exists : + 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); @@ -2506,12 +3106,13 @@ public class FHIRPathEngine { s = s || pt.isSystemType(); c = c || !pt.isSystemType(); } - if (s && c) + if (s && c) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); - else if (s) + } else if (s) { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); - else + } else { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); + } } case Is : { checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); @@ -2556,60 +3157,66 @@ public class FHIRPathEngine { case Iif : { TypeDetails types = new TypeDetails(null); types.update(paramTypes.get(0)); - if (paramTypes.size() > 1) + if (paramTypes.size() > 1) { types.update(paramTypes.get(1)); + } return types; } case Lower : { - checkContextString(focus, "lower", exp); + checkContextString(focus, "lower", exp, true); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } case Upper : { - checkContextString(focus, "upper", exp); + checkContextString(focus, "upper", exp, true); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } case ToChars : { - checkContextString(focus, "toChars", exp); + checkContextString(focus, "toChars", exp, true); return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); } case IndexOf : { - checkContextString(focus, "indexOf", exp); + 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); } case Substring : { - checkContextString(focus, "subString", exp); + 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); } case StartsWith : { - checkContextString(focus, "startsWith", exp); + 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); } case EndsWith : { - checkContextString(focus, "endsWith", exp); + 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); } case Matches : { - checkContextString(focus, "matches", exp); + 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); + } + 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); } case ReplaceMatches : { - checkContextString(focus, "replaceMatches", exp); + 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); } case Contains : { - checkContextString(focus, "contains", exp); + checkContextString(focus, "contains", exp, true); checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } case Replace : { - checkContextString(focus, "replace", exp); - checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + 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)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); } case Length : { @@ -2655,14 +3262,36 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); case HasValue : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); - case HtmlChecks : + case HtmlChecks1 : + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); + case HtmlChecks2 : 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); case AliasAs : checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); - return focus; + return focus; + case Encode: + 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)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + case Escape: + 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)); + 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)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); + case Join: + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); case ToInteger : { checkContextPrimitive(focus, "toInteger", true, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); @@ -2684,11 +3313,11 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } case ToDateTime : { - checkContextPrimitive(focus, "toBoolean", false, exp); + checkContextPrimitive(focus, "ToDateTime", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); } case ToTime : { - checkContextPrimitive(focus, "toBoolean", false, exp); + checkContextPrimitive(focus, "ToTime", false, exp); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); } case ConvertsToString : @@ -2699,6 +3328,7 @@ public class FHIRPathEngine { case ConvertsToInteger : case ConvertsToDecimal : case ConvertsToDateTime : + case ConvertsToDate : case ConvertsToTime : case ConvertsToBoolean : { checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp); @@ -2708,6 +3338,61 @@ public class FHIRPathEngine { checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); } + case Abs : { + checkContextNumerical(focus, "abs", exp); + return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); + } + case Truncate : + case Floor : + case Ceiling : { + checkContextDecimal(focus, exp.getFunction().toCode(), exp); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); + } + + case Round :{ + checkContextDecimal(focus, "round", exp); + if (paramTypes.size() > 0) { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + } + 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 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); + if (paramTypes.size() > 0) { + checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); + } + if (focus.hasType("decimal") && (focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); + } else if (focus.hasType("decimal")) { + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); + } else { + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); + } + } + case Precision: { + checkContextContinuous(focus, exp.getFunction().toCode(), exp); + return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); + } + case Custom : { return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); } @@ -2717,57 +3402,96 @@ public class FHIRPathEngine { throw new Error("not Implemented yet"); } + 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; + case 1: + return exp.getFunction() == Function.Trace; + default: + return false; + } + } + private void checkParamTypes(ExpressionNode expr, String funcName, List paramTypes, TypeDetails... typeSet) throws PathEngineException { int i = 0; for (TypeDetails pt : typeSet) { - if (i == paramTypes.size()) + if (i == paramTypes.size()) { return; + } TypeDetails actual = paramTypes.get(i); i++; for (String a : actual.getTypes()) { - if (!pt.hasType(worker, a)) - throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString(), expr.getStart(), expr.toString()); + if (!pt.hasType(worker, a)) { + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString()); + } } } } private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { - if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) - throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections", expr.getStart(), expr.toString()); + if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) { + throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name); + } } 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")) - throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, canonical, Reference", expr.getStart(), expr.toString()); + if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !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")) - throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept", expr.getStart(), expr.toString()); + 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) throws PathEngineException { - if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) - throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe(), expr.getStart(), expr.toString()); + 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 checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException { - if (canQty) { - if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) - throw new PathEngineException("The function '"+name+"'() can only be used on a Quantity or on "+primitiveTypes.toString(), expr.getStart(), expr.toString()); - } else if (!focus.hasType(primitiveTypes)) - throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString(), expr.getStart(), expr.toString()); + 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()); + } + } else if (!focus.hasType(primitiveTypes)) { + 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")) { + 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) throws PathEngineException { + if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time")) { + throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); + } + } private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException { TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); - for (String f : focus.getTypes()) + for (String f : focus.getTypes()) { getChildTypesByName(f, mask, result, expr); + } return result; } @@ -2779,7 +3503,7 @@ public class FHIRPathEngine { // 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); @@ -2818,6 +3542,7 @@ public class FHIRPathEngine { 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); @@ -2837,8 +3562,16 @@ public class FHIRPathEngine { case AllTrue: return funcAllTrue(context, focus, exp); case HasValue : return funcHasValue(context, focus, exp); case AliasAs : return funcAliasAs(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 Alias : return funcAlias(context, focus, exp); - case HtmlChecks : return funcHtmlChecks(context, focus, exp); + case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp); + case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); case ToInteger : return funcToInteger(context, focus, exp); case ToDecimal : return funcToDecimal(context, focus, exp); case ToString : return funcToString(context, focus, exp); @@ -2852,12 +3585,29 @@ public class FHIRPathEngine { 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 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 Custom: { List> params = new ArrayList>(); - for (ExpressionNode p : exp.getParameters()) + for (ExpressionNode p : exp.getParameters()) { params.add(execute(context, focus, p, true)); + } return hostServices.executeFunction(context.appInfo, focus, exp.getName(), params); } default: @@ -2865,7 +3615,436 @@ public class FHIRPathEngine { } } - private List funcAliasAs(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcSqrt(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new DecimalType(Math.sqrt(d))); + } catch (Exception e) { + // just return nothing + } + } else { + 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()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new DecimalType(Math.abs(d))); + } catch (Exception e) { + // just return nothing + } + } else if (base.hasType("Quantity")) { + 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"); + } + 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()); + } + Base base = focus.get(0); + 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))); + } catch (Exception e) { + // just return nothing + } + } else { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal"); + } + return result; + } + + private List funcFloor(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new IntegerType((int) Math.floor(d))); + } catch (Exception e) { + // just return nothing + } + } else { + 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(); + } + if (focus.size() > 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new DecimalType(Math.exp(d))); + } catch (Exception e) { + // just return nothing + } + + } else { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal"); + } + 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()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + Double d = Double.parseDouble(base.primitiveValue()); + try { + 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"); + } + 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()); + } + Base base = focus.get(0); + List result = new ArrayList(); + 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"); + } + Double e = Double.parseDouble(n1.get(0).primitiveValue()); + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new DecimalType(customLog(e, d))); + } catch (Exception ex) { + // just return nothing + } + } else { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal"); + } + return result; + } + + private static double customLog(double base, double logNumber) { + return Math.log(logNumber) / Math.log(base); + } + + private List funcPower(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + 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"); + } + Double e = Double.parseDouble(n1.get(0).primitiveValue()); + Double d = Double.parseDouble(base.primitiveValue()); + try { + result.add(new DecimalType(Math.pow(d, e))); + } catch (Exception ex) { + // just return nothing + } + } else { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal"); + } + return result; + } + + private List funcTruncate(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + String s = base.primitiveValue(); + if (s.contains(".")) { + s = s.substring(0, s.indexOf(".")); + } + result.add(new IntegerType(s)); + } else { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); + } + return result; + } + + private List funcLowBoundary(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); + } + int precision = 0; + if (expr.getParameters().size() > 0) { + List n1 = execute(context, focus, expr.getParameters().get(0), true); + if (n1.size() != 1) { + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); + } + precision = Integer.parseInt(n1.get(0).primitiveValue()); + } + + Base base = focus.get(0); + List result = new ArrayList(); + + if (base.hasType("decimal")) { + result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); + } else if (base.hasType("date")) { + result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); + } else if (base.hasType("dateTime")) { + 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 { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); + } + return result; + } + + private List funcHighBoundary(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); + } + int precision = 0; + if (expr.getParameters().size() > 0) { + List n1 = execute(context, focus, expr.getParameters().get(0), true); + if (n1.size() != 1) { + throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); + } + precision = Integer.parseInt(n1.get(0).primitiveValue()); + } + + + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("decimal")) { + result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); + } else if (base.hasType("date")) { + result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); + } else if (base.hasType("dateTime")) { + 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 { + makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); + } + return result; + } + + private List funcPrecision(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("decimal")) { + result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue()))); + } else if (base.hasType("date") || base.hasType("dateTime")) { + result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue()))); + } 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"); + } + return result; + } + + private List funcRound(ExecutionContext context, List focus, ExpressionNode expr) { + if (focus.size() != 1) { + throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size()); + } + Base base = focus.get(0); + List result = new ArrayList(); + if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { + int i = 0; + 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"); + } + i = Integer.parseInt(n1.get(0).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"); + } + return result; + } + + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + public static byte[] hexStringToByteArray(String s) { + 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)); + } + return data; + } + + private List funcEncode(ExecutionContext context, List focus, ExpressionNode exp) { + List nl = execute(context, focus, exp.getParameters().get(0), true); + 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(bytesToHex(cnt.getBytes()))); + } else if ("base64".equals(param)) { + Base64.Encoder enc = Base64.getEncoder(); + result.add(new StringType(enc.encodeToString(cnt.getBytes()))); + } else if ("urlbase64".equals(param)) { + Base64.Encoder enc = Base64.getUrlEncoder(); + result.add(new StringType(enc.encodeToString(cnt.getBytes()))); + } + } + return result; + } + + private List funcDecode(ExecutionContext context, List focus, ExpressionNode exp) { + List nl = execute(context, focus, exp.getParameters().get(0), true); + 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)))); + } else if ("base64".equals(param)) { + Base64.Decoder enc = Base64.getDecoder(); + result.add(new StringType(new String(enc.decode(cnt)))); + } else if ("urlbase64".equals(param)) { + Base64.Decoder enc = Base64.getUrlDecoder(); + result.add(new StringType(new String(enc.decode(cnt)))); + } + } + + return result; + } + + private List funcEscape(ExecutionContext context, List focus, ExpressionNode exp) { + List nl = execute(context, focus, exp.getParameters().get(0), true); + String param = nl.get(0).primitiveValue(); + + List result = new ArrayList(); + if (focus.size() == 1) { + String cnt = focus.get(0).primitiveValue(); + if ("html".equals(param)) { + result.add(new StringType(Utilities.escapeXml(cnt))); + } else if ("json".equals(param)) { + result.add(new StringType(Utilities.escapeJson(cnt))); + } + } + + return result; + } + + private List funcUnescape(ExecutionContext context, List focus, ExpressionNode exp) { + List nl = execute(context, focus, exp.getParameters().get(0), true); + String param = nl.get(0).primitiveValue(); + + List result = new ArrayList(); + if (focus.size() == 1) { + String cnt = focus.get(0).primitiveValue(); + if ("html".equals(param)) { + result.add(new StringType(Utilities.unescapeXml(cnt))); + } else if ("json".equals(param)) { + result.add(new StringType(Utilities.unescapeJson(cnt))); + } + } + + return result; + } + + private List funcTrim(ExecutionContext context, List focus, ExpressionNode exp) { + List result = new ArrayList(); + if (focus.size() == 1) { + String cnt = focus.get(0).primitiveValue(); + result.add(new StringType(cnt.trim())); + } + return result; + } + + private List funcSplit(ExecutionContext context, List focus, ExpressionNode exp) { + List nl = execute(context, focus, exp.getParameters().get(0), true); + String param = nl.get(0).primitiveValue(); + + List result = new ArrayList(); + if (focus.size() == 1) { + String cnt = focus.get(0).primitiveValue(); + for (String s : cnt.split(param)) { + result.add(new StringType(s)); + } + } + 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(); + + List result = new ArrayList(); + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param); + for (Base i : focus) { + b.append(i.primitiveValue()); + } + result.add(new StringType(b.toString())); + return result; + } + + private List funcAliasAs(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List nl = execute(context, focus, exp.getParameters().get(0), true); String name = nl.get(0).primitiveValue(); context.addAlias(name, focus); @@ -2877,17 +4056,87 @@ public class FHIRPathEngine { String name = nl.get(0).primitiveValue(); List res = new ArrayList(); Base b = context.getAlias(name); - if (b != null) + if (b != null) { res.add(b); + } return res; } - private List funcHtmlChecks(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcHtmlChecks1(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { // todo: actually check the HTML - return makeBoolean(true); + if (focus.size() != 1) { + return makeBoolean(false); + } + XhtmlNode x = focus.get(0).getXhtml(); + if (x == null) { + return makeBoolean(false); + } + return makeBoolean(checkHtmlNames(x)); + } + + private List funcHtmlChecks2(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + // todo: actually check the HTML + if (focus.size() != 1) { + return makeBoolean(false); + } + XhtmlNode x = focus.get(0).getXhtml(); + if (x == null) { + return makeBoolean(false); + } + 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()))) { + return true; + } + for (XhtmlNode c : x.getChildNodes()) { + if (checkForContent(c)) { + return true; + } + } + return false; + } + + + private boolean checkHtmlNames(XhtmlNode node) { + 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; + } + for (String an : node.getAttributes().keySet()) { + boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, + "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex", + // tables + "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" + ); + if (!ok) { + return false; + } + } + for (XhtmlNode c : node.getChildNodes()) { + if (!checkHtmlNames(c)) { + return false; + } + } + } + return true; } - private List funcAll(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); if (exp.getParameters().size() == 1) { @@ -2906,7 +4155,7 @@ public class FHIRPathEngine { } else {// (exp.getParameters().size() == 0) { boolean all = true; for (Base item : focus) { - Equality eq = asBool(item); + Equality eq = asBool(item, true); if (eq != Equality.True) { all = false; break; @@ -2942,7 +4191,27 @@ public class FHIRPathEngine { private List funcMemberOf(ExecutionContext context, List focus, ExpressionNode exp) { - throw new Error("not Implemented yet"); + List nl = execute(context, focus, exp.getParameters().get(0), true); + if (nl.size() != 1 || focus.size() != 1) { + return new ArrayList(); + } + + String url = nl.get(0).primitiveValue(); + ValueSet vs = hostServices != null ? hostServices.resolveValueSet(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.guessSystem(), 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()); + return new ArrayList(); + } } @@ -2968,38 +4237,34 @@ public class FHIRPathEngine { private List funcChildren(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - for (Base b : focus) + for (Base b : focus) { getChildrenByName(b, "*", result); + } return result; } - private List funcReplace(ExecutionContext context, List focus, ExpressionNode exp) 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); + List rB = execute(context, focus, expr.getParameters().get(1), true); + String r = convertToString(rB); - if (focus.size() == 1) { - String f = convertToString(focus.get(0)); - - if (!Utilities.noString(f)) { - - if (exp.getParameters().size() == 2) { - - String t = convertToString(execute(context, focus, exp.getParameters().get(0), true)); - String r = convertToString(execute(context, focus, exp.getParameters().get(1), true)); - + if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) { + // + } else if (focus.size() == 1) { + if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + String f = convertToString(focus.get(0)); + if (Utilities.noString(f)) { + result.add(new StringType("")); + } else { String n = f.replace(t, r); result.add(new StringType(n)); } - else { - throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size()), exp.getStart(), exp.toString()); - } } - else { - throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item"), exp.getStart(), exp.toString()); - } - } - else { - throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size()), exp.getStart(), exp.toString()); + } else { + throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size()); } return result; } @@ -3007,30 +4272,41 @@ public class FHIRPathEngine { private List funcReplaceMatches(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true)); - String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true)); + List regexB = execute(context, focus, exp.getParameters().get(0), true); + String regex = convertToString(regexB); + List replB = execute(context, focus, exp.getParameters().get(1), true); + String repl = convertToString(replB); - if (focus.size() == 1 && !Utilities.noString(regex)) - result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); - else + if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) { + // + } else if (focus.size() == 1 && !Utilities.noString(regex)) { + if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); + } + } else { result.add(new StringType(convertToString(focus.get(0))).noExtensions()); + } return result; } private List funcEndsWith(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); + List swb = execute(context, focus, exp.getParameters().get(0), true); + String sw = convertToString(swb); - if (focus.size() == 0) - result.add(new BooleanType(false).noExtensions()); - else if (Utilities.noString(sw)) + if (focus.size() == 0) { + // + } else if (swb.size() == 0) { + // + } else if (Utilities.noString(sw)) { result.add(new BooleanType(true).noExtensions()); - else { - if (focus.size() == 1 && !Utilities.noString(sw)) + } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + if (focus.size() == 1 && !Utilities.noString(sw)) { result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); - else + } else { result.add(new BooleanType(false).noExtensions()); + } } return result; } @@ -3045,24 +4321,27 @@ public class FHIRPathEngine { private List funcToBoolean(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); if (focus.size() == 1) { - if (focus.get(0) instanceof BooleanType) + if (focus.get(0) instanceof BooleanType) { result.add(focus.get(0)); - else if (focus.get(0) instanceof IntegerType) { + } else if (focus.get(0) instanceof IntegerType) { int i = Integer.parseInt(focus.get(0).primitiveValue()); - if (i == 0) + if (i == 0) { result.add(new BooleanType(false).noExtensions()); - else if (i == 1) + } else if (i == 1) { result.add(new BooleanType(true).noExtensions()); + } } else if (focus.get(0) instanceof DecimalType) { - if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) + if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) { result.add(new BooleanType(false).noExtensions()); - else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) + } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) { result.add(new BooleanType(true).noExtensions()); + } } else if (focus.get(0) instanceof StringType) { - if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) + if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) { result.add(new BooleanType(true).noExtensions()); - else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) + } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) { result.add(new BooleanType(false).noExtensions()); + } } } return result; @@ -3071,12 +4350,13 @@ public class FHIRPathEngine { private List funcToQuantity(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); if (focus.size() == 1) { - if (focus.get(0) instanceof Quantity) + if (focus.get(0) instanceof Quantity) { result.add(focus.get(0)); - else if (focus.get(0) instanceof StringType) { + } else if (focus.get(0) instanceof StringType) { Quantity q = parseQuantityString(focus.get(0).primitiveValue()); - if (q != null) + if (q != null) { 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()); } else if (focus.get(0) instanceof DecimalType) { @@ -3086,30 +4366,33 @@ public class FHIRPathEngine { return result; } - private List funcToDateTime(ExecutionContext context, List focus, ExpressionNode exp) { -// List result = new ArrayList(); -// result.add(new BooleanType(convertToBoolean(focus))); -// return result; - throw new NotImplementedException("funcToDateTime is not implemented"); -} + private List funcToDateTime(ExecutionContext context, List focus, ExpressionNode expr) { + // 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 exp) { -// List result = new ArrayList(); -// result.add(new BooleanType(convertToBoolean(focus))); -// return result; - throw new NotImplementedException("funcToTime is not implemented"); -} + private List funcToTime(ExecutionContext context, List focus, ExpressionNode expr) { + // 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 exp) { + private List funcToDecimal(ExecutionContext context, List focus, ExpressionNode expr) { String s = convertToString(focus); List result = new ArrayList(); - if (Utilities.isDecimal(s, true)) + if (Utilities.isDecimal(s, true)) { result.add(new DecimalType(s).noExtensions()); - if ("true".equals(s)) + } + if ("true".equals(s)) { result.add(new DecimalType(1).noExtensions()); - if ("false".equals(s)) + } + if ("false".equals(s)) { result.add(new DecimalType(0).noExtensions()); + } return result; } @@ -3118,12 +4401,13 @@ public class FHIRPathEngine { List n1 = execute(context, focus, exp.getParameters().get(0), true); Equality v = asBool(n1, exp); - if (v == Equality.True) + if (v == Equality.True) { return execute(context, focus, exp.getParameters().get(1), true); - else if (exp.getParameters().size() < 3) + } else if (exp.getParameters().size() < 3) { return new ArrayList(); - else + } else { return execute(context, focus, exp.getParameters().get(2), true); + } } @@ -3132,8 +4416,9 @@ public class FHIRPathEngine { int i1 = Integer.parseInt(n1.get(0).primitiveValue()); List result = new ArrayList(); - for (int i = 0; i < Math.min(focus.size(), i1); i++) + for (int i = 0; i < Math.min(focus.size(), i1); i++) { result.add(focus.get(i)); + } return result; } @@ -3141,12 +4426,14 @@ public class FHIRPathEngine { private List funcUnion(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); for (Base item : focus) { - if (!doContains(result, item)) + if (!doContains(result, item)) { result.add(item); + } } - for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { - if (!doContains(result, item)) + for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { + if (!doContains(result, item)) { result.add(item); + } } return result; } @@ -3165,10 +4452,11 @@ public class FHIRPathEngine { 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); - + for (Base item : focus) { - if (!doContains(result, item) && doContains(other, item)) + if (!doContains(result, item) && doContains(other, item)) { result.add(item); + } } return result; } @@ -3176,37 +4464,42 @@ public class FHIRPathEngine { private List funcExclude(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List other = execute(context, focus, exp.getParameters().get(0), true); - + for (Base item : focus) { - if (!doContains(other, item)) + if (!doContains(other, item)) { result.add(item); + } } return result; } - private List funcSingle(ExecutionContext context, List focus, ExpressionNode exp) throws PathEngineException { - if (focus.size() == 1) + private List funcSingle(ExecutionContext context, List focus, ExpressionNode expr) throws PathEngineException { + if (focus.size() == 1) { return focus; - throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()), exp.getStart(), exp.toString()); + } + throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size()); } - private List funcIs(ExecutionContext context, List focus, ExpressionNode exp) throws PathEngineException { - if (focus.size() == 0 || focus.size() > 1) - return makeBoolean(false); + private List funcIs(ExecutionContext context, List focus, ExpressionNode expr) throws PathEngineException { + if (focus.size() == 0 || focus.size() > 1) { + return makeNull(); + } String ns = null; String n = null; - - ExpressionNode texp = exp.getParameters().get(0); - if (texp.getKind() != Kind.Name) - throw new PathEngineException("Unsupported Expression type for Parameter on Is", exp.getStart(), exp.toString()); + + ExpressionNode texp = expr.getParameters().get(0); + if (texp.getKind() != Kind.Name) { + throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is"); + } if (texp.getInner() != null) { - if (texp.getInner().getKind() != Kind.Name) - throw new PathEngineException("Unsupported Expression type for Parameter on Is", exp.getStart(), exp.toString()); + if (texp.getInner().getKind() != Kind.Name) { + throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is"); + } ns = texp.getName(); n = texp.getInner().getName(); - } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { + } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) { ns = "System"; n = texp.getName(); } else { @@ -3214,35 +4507,71 @@ public class FHIRPathEngine { n = texp.getName(); } if (ns.equals("System")) { - if (focus.get(0) instanceof Resource) + if (focus.get(0) instanceof Resource) { return makeBoolean(false); - if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) - return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType()))); - else + } + if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) { + String t = Utilities.capitalize(focus.get(0).fhirType()); + if (n.equals(t)) { + return makeBoolean(true); + } + if ("Date".equals(t) && n.equals("DateTime")) { + return makeBoolean(true); + } else { + return makeBoolean(false); + } + } else { return makeBoolean(false); + } } else if (ns.equals("FHIR")) { - return makeBoolean(n.equals(focus.get(0).fhirType())); + if (n.equals(focus.get(0).fhirType())) { + return makeBoolean(true); + } else { + StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); + while (sd != null) { + if (n.equals(sd.getType())) { + return makeBoolean(true); + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return makeBoolean(false); + } } else { return makeBoolean(false); } } - private List funcAs(ExecutionContext context, List focus, ExpressionNode exp) { + private List funcAs(ExecutionContext context, List focus, ExpressionNode expr) { List result = new ArrayList(); 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 (expr.getParameters().get(0).getInner() != null) { + tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); + } else { + tn = "FHIR."+expr.getParameters().get(0).getName(); + } for (Base b : focus) { if (tn.startsWith("System.")) { - if (b instanceof Element &&((Element) b).isDisallowExtensions()) - if (b.hasType(tn.substring(7))) - result.add(b); - } else if (tn.startsWith("FHIR.")) { - if (b.hasType(tn.substring(5))) + if (b instanceof Element &&((Element) b).isDisallowExtensions()) { + if (b.hasType(tn.substring(7))) { result.add(b); + } + } + + } else if (tn.startsWith("FHIR.")) { + String tnp = tn.substring(5); + 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 = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + } } } return result; @@ -3250,8 +4579,9 @@ public class FHIRPathEngine { private List funcType(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - for (Base item : focus) + for (Base item : focus) { result.add(new ClassTypeInfo(item)); + } return result; } @@ -3270,10 +4600,21 @@ public class FHIRPathEngine { pc.add(item); added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); } - more = !added.isEmpty(); - result.addAll(added); + more = false; current.clear(); - current.addAll(added); + for (Base b : added) { + boolean isnew = true; + for (Base t : result) { + if (b.equalsDeep(t)) { + isnew = false; + } + } + if (isnew) { + result.add(b); + current.add(b); + more = true; + } + } } return result; } @@ -3281,13 +4622,15 @@ public class FHIRPathEngine { private List funcAggregate(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List total = new ArrayList(); - if (exp.parameterCount() > 1) + if (exp.parameterCount() > 1) { total = execute(context, focus, exp.getParameters().get(1), false); + } List pc = new ArrayList(); for (Base item : focus) { ExecutionContext c = changeThis(context, item); c.total = total; + c.next(); total = execute(c, pc, exp.getParameters().get(0), true); } return total; @@ -3296,10 +4639,12 @@ public class FHIRPathEngine { private List funcIsDistinct(ExecutionContext context, List focus, ExpressionNode exp) { - if (focus.size() < 1) + if (focus.size() < 1) { return makeBoolean(true); - if (focus.size() == 1) + } + if (focus.size() == 1) { return makeBoolean(true); + } boolean distinct = true; for (int i = 0; i < focus.size(); i++) { @@ -3366,9 +4711,19 @@ public class FHIRPathEngine { private List funcExists(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); boolean empty = true; - for (Base f : focus) - if (!f.isEmpty()) + List pc = new ArrayList(); + for (Base f : focus) { + if (exp.getParameters().size() == 1) { + pc.clear(); + pc.add(f); + Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp); + if (v == Equality.True) { + empty = false; + } + } else if (!f.isEmpty()) { empty = false; + } + } result.add(new BooleanType(!empty).noExtensions()); return result; } @@ -3376,33 +4731,44 @@ public class FHIRPathEngine { private List funcResolve(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); + Base refContext = null; for (Base item : focus) { String s = convertToString(item); if (item.fhirType().equals("Reference")) { + refContext = item; Property p = item.getChildByName("reference"); - if (p != null && p.hasValues()) + if (p != null && p.hasValues()) { s = convertToString(p.getValues().get(0)); - else + } else { s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) + } } if (item.fhirType().equals("canonical")) { s = item.primitiveValue(); + refContext = item; } if (s != null) { Base res = null; if (s.startsWith("#")) { Property p = context.rootResource.getChildByName("contained"); - for (Base c : p.getValues()) { - if (chompHash(s).equals(chompHash(c.getIdBase()))) { - res = c; - break; + if (p != null) { + for (Base c : p.getValues()) { + if (chompHash(s).equals(chompHash(c.getIdBase()))) { + res = c; + break; + } } } } else if (hostServices != null) { - res = hostServices.resolveReference(context.appInfo, s); + try { + res = hostServices.resolveReference(context.appInfo, s, refContext); + } catch (Exception e) { + res = null; + } } - if (res != null) + if (res != null) { result.add(res); + } } } @@ -3432,157 +4798,179 @@ public class FHIRPathEngine { for (Base ex : ext) { List vl = new ArrayList(); getChildrenByName(ex, "url", vl); - if (convertToString(vl).equals(url)) + if (convertToString(vl).equals(url)) { result.add(ex); + } } } return result; } - private List funcAllFalse(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List result = new ArrayList(); - if (exp.getParameters().size() == 1) { - boolean all = true; - List pc = new ArrayList(); - for (Base item : focus) { - pc.clear(); - pc.add(item); - List res = execute(context, pc, exp.getParameters().get(0), true); - Equality v = asBool(res, exp); - if (v != Equality.False) { - all = false; - break; - } - } - result.add(new BooleanType(all).noExtensions()); - } else { - boolean all = true; - for (Base item : focus) { - Equality v = asBool(item); - if (v != Equality.False) { - all = false; - break; - } - } - result.add(new BooleanType(all).noExtensions()); - } - return result; - } - - private List funcAnyFalse(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List result = new ArrayList(); - if (exp.getParameters().size() == 1) { - boolean any = false; - List pc = new ArrayList(); - for (Base item : focus) { - pc.clear(); - pc.add(item); - List res = execute(context, pc, exp.getParameters().get(0), true); - Equality v = asBool(res, exp); - if (v == Equality.False) { - any = true; - break; - } - } - result.add(new BooleanType(any).noExtensions()); - } else { - boolean any = false; - for (Base item : focus) { - Equality v = asBool(item); - if (v == Equality.False) { - any = true; - break; - } - } - result.add(new BooleanType(any).noExtensions()); - } - return result; - } - - private List funcAllTrue(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List result = new ArrayList(); - if (exp.getParameters().size() == 1) { - boolean all = true; - List pc = new ArrayList(); - for (Base item : focus) { - pc.clear(); - pc.add(item); - List res = execute(context, pc, exp.getParameters().get(0), true); - Equality v = asBool(res, exp); - if (v != Equality.True) { - all = false; - break; - } - } - result.add(new BooleanType(all).noExtensions()); - } else { - boolean all = true; - for (Base item : focus) { - Equality v = asBool(item); - if (v != Equality.True) { - all = false; - break; - } - } - result.add(new BooleanType(all).noExtensions()); - } - return result; - } - - private List funcAnyTrue(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List result = new ArrayList(); - if (exp.getParameters().size() == 1) { - boolean any = false; - List pc = new ArrayList(); - for (Base item : focus) { - pc.clear(); - pc.add(item); - List res = execute(context, pc, exp.getParameters().get(0), true); - Equality v = asBool(res, exp); - if (v == Equality.True) { - any = true; - break; - } - } - result.add(new BooleanType(any).noExtensions()); - } else { - boolean any = false; + private List funcAllFalse(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + List result = new ArrayList(); + if (exp.getParameters().size() == 1) { + boolean all = true; + List pc = new ArrayList(); for (Base item : focus) { - Equality v = asBool(item); - if (v == Equality.True) { - any = true; - break; - } + pc.clear(); + pc.add(item); + List res = execute(context, pc, exp.getParameters().get(0), true); + Equality v = asBool(res, exp); + if (v != Equality.False) { + all = false; + break; + } + } + result.add(new BooleanType(all).noExtensions()); + } else { + boolean all = true; + for (Base item : focus) { + if (!canConvertToBoolean(item)) { + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); + } + + Equality v = asBool(item, true); + if (v != Equality.False) { + all = false; + break; + } + } + result.add(new BooleanType(all).noExtensions()); + } + return result; + } + + private List funcAnyFalse(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + List result = new ArrayList(); + if (exp.getParameters().size() == 1) { + boolean any = false; + List pc = new ArrayList(); + for (Base item : focus) { + pc.clear(); + pc.add(item); + List res = execute(context, pc, exp.getParameters().get(0), true); + Equality v = asBool(res, exp); + if (v == Equality.False) { + any = true; + break; + } } result.add(new BooleanType(any).noExtensions()); - } - return result; - } + } else { + boolean any = false; + for (Base item : focus) { + if (!canConvertToBoolean(item)) { + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); + } - private List funcTrace(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + Equality v = asBool(item, true); + if (v == Equality.False) { + any = true; + break; + } + } + result.add(new BooleanType(any).noExtensions()); + } + return result; + } + + private List funcAllTrue(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + List result = new ArrayList(); + if (exp.getParameters().size() == 1) { + boolean all = true; + List pc = new ArrayList(); + for (Base item : focus) { + pc.clear(); + pc.add(item); + List res = execute(context, pc, exp.getParameters().get(0), true); + Equality v = asBool(res, exp); + if (v != Equality.True) { + all = false; + break; + } + } + result.add(new BooleanType(all).noExtensions()); + } else { + boolean all = true; + for (Base item : focus) { + if (!canConvertToBoolean(item)) { + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); + } + Equality v = asBool(item, true); + if (v != Equality.True) { + all = false; + break; + } + } + result.add(new BooleanType(all).noExtensions()); + } + return result; + } + + private List funcAnyTrue(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + List result = new ArrayList(); + if (exp.getParameters().size() == 1) { + boolean any = false; + List pc = new ArrayList(); + for (Base item : focus) { + pc.clear(); + pc.add(item); + List res = execute(context, pc, exp.getParameters().get(0), true); + Equality v = asBool(res, exp); + if (v == Equality.True) { + any = true; + break; + } + } + result.add(new BooleanType(any).noExtensions()); + } else { + boolean any = false; + for (Base item : focus) { + if (!canConvertToBoolean(item)) { + throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); + } + + Equality v = asBool(item, true); + if (v == Equality.True) { + any = true; + break; + } + } + result.add(new BooleanType(any).noExtensions()); + } + return result; + } + + private boolean canConvertToBoolean(Base item) { + return (item.isBooleanPrimitive()); + } + + private List funcTrace(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List nl = execute(context, focus, exp.getParameters().get(0), true); String name = nl.get(0).primitiveValue(); 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; } - private List funcCheck(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List n1 = execute(context, focus, exp.getParameters().get(0), true); + private List funcCheck(ExecutionContext context, List focus, ExpressionNode expr) throws FHIRException { + List n1 = execute(context, focus, expr.getParameters().get(0), true); if (!convertToBoolean(n1)) { - List n2 = execute(context, focus, exp.getParameters().get(1), true); + List n2 = execute(context, focus, expr.getParameters().get(1), true); String name = n2.get(0).primitiveValue(); - throw new FHIRException("check failed: "+name); + throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name); } return focus; } private List funcDistinct(ExecutionContext context, List focus, ExpressionNode exp) { - if (focus.size() <= 1) + if (focus.size() <= 1) { return focus; + } List result = new ArrayList(); for (int i = 0; i < focus.size(); i++) { @@ -3596,50 +4984,91 @@ public class FHIRPathEngine { break; } } - if (!found) + if (!found) { result.add(focus.get(i)); + } } return result; } - private List funcMatches(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcMatches(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); + + if (focus.size() == 0 || swb.size() == 0) { + // + } else if (focus.size() == 1 && !Utilities.noString(sw)) { + if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + String st = convertToString(focus.get(0)); + if (Utilities.noString(st)) { + result.add(new BooleanType(false).noExtensions()); + } else { + Pattern p = Pattern.compile("(?s)" + sw); + Matcher m = p.matcher(st); + boolean ok = m.find(); + result.add(new BooleanType(ok).noExtensions()); + } + } + } else { + result.add(new BooleanType(false).noExtensions()); + } + return result; + } + + 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)); if (focus.size() == 1 && !Utilities.noString(sw)) { - String st = convertToString(focus.get(0)); - if (Utilities.noString(st)) - result.add(new BooleanType(false).noExtensions()); - else { - boolean ok = st.matches(sw); - result.add(new BooleanType(ok).noExtensions()); + if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + String st = convertToString(focus.get(0)); + if (Utilities.noString(st)) { + result.add(new BooleanType(false).noExtensions()); + } else { + Pattern p = Pattern.compile("(?s)" + sw); + Matcher m = p.matcher(st); + boolean ok = m.matches(); + result.add(new BooleanType(ok).noExtensions()); + } } - } else + } else { result.add(new BooleanType(false).noExtensions()); + } return result; } - private List funcContains(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcContains(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); + List swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); + String sw = convertToString(swb); if (focus.size() != 1) { - result.add(new BooleanType(false).noExtensions()); + // + } else if (swb.size() != 1) { + // } else if (Utilities.noString(sw)) { result.add(new BooleanType(true).noExtensions()); - } else { + } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { String st = convertToString(focus.get(0)); - if (Utilities.noString(st)) + if (Utilities.noString(st)) { result.add(new BooleanType(false).noExtensions()); - else + } else { result.add(new BooleanType(st.contains(sw)).noExtensions()); + } } return result; } + private List baseToList(Base b) { + List res = new ArrayList<>(); + res.add(b); + return res; + } + private List funcLength(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() == 1) { + if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { String s = convertToString(focus.get(0)); result.add(new IntegerType(s.length()).noExtensions()); } @@ -3651,98 +5080,116 @@ public class FHIRPathEngine { if (focus.size() == 1) { String s = convertToString(focus.get(0)); result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); - } else + } else { result.add(new BooleanType(false).noExtensions()); + } 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(); - String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); + List swb = execute(context, focus, exp.getParameters().get(0), true); + String sw = convertToString(swb); if (focus.size() == 0) { - result.add(new BooleanType(false).noExtensions()); + // no result + } else if (swb.size() == 0) { + // no result } else if (Utilities.noString(sw)) { result.add(new BooleanType(true).noExtensions()); - } else { + } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { String s = convertToString(focus.get(0)); - if (s == null) + if (s == null) { result.add(new BooleanType(false).noExtensions()); - else + } else { result.add(new BooleanType(s.startsWith(sw)).noExtensions()); + } } return result; } private List funcLower(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - if (focus.size() == 1) { + 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()); + } } return result; } private List funcUpper(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - if (focus.size() == 1) { + 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()); + } } return result; } private List funcToChars(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); - if (focus.size() == 1) { + 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()); - } - return result; - } - - private List funcIndexOf(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { - List result = new ArrayList(); - - String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); - if (focus.size() == 0) { - result.add(new IntegerType(0).noExtensions()); - } else if (Utilities.noString(sw)) { - result.add(new IntegerType(0).noExtensions()); - } else { - String s = convertToString(focus.get(0)); - if (s == null) - result.add(new IntegerType(0).noExtensions()); - else - result.add(new IntegerType(s.indexOf(sw)).noExtensions()); + } } return result; } - private List funcSubstring(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcIndexOf(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); + if (focus.size() == 0) { + // no result + } else if (swb.size() == 0) { + // no result + } else if (Utilities.noString(sw)) { + result.add(new IntegerType(0).noExtensions()); + } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { + String s = convertToString(focus.get(0)); + if (s == null) { + result.add(new IntegerType(0).noExtensions()); + } else { + result.add(new IntegerType(s.indexOf(sw)).noExtensions()); + } + } + return result; + } + + 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())) { + return new ArrayList(); + } i2 = Integer.parseInt(n2.get(0).primitiveValue()); } - if (focus.size() == 1) { + if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { String sw = convertToString(focus.get(0)); String s; - if (i1 < 0 || i1 >= sw.length()) + if (i1 < 0 || i1 >= sw.length()) { return new ArrayList(); - if (exp.parameterCount() == 2) + } + if (exp.parameterCount() == 2) { s = sw.substring(i1, Math.min(sw.length(), i1+i2)); - else + } else { s = sw.substring(i1); - if (!Utilities.noString(s)) + } + if (!Utilities.noString(s)) { result.add(new StringType(s).noExtensions()); + } } return result; } @@ -3750,69 +5197,89 @@ public class FHIRPathEngine { private List funcToInteger(ExecutionContext context, List focus, ExpressionNode exp) { String s = convertToString(focus); List result = new ArrayList(); - if (Utilities.isInteger(s)) + if (Utilities.isInteger(s)) { result.add(new IntegerType(s).noExtensions()); - else if ("true".equals(s)) + } else if ("true".equals(s)) { result.add(new IntegerType(1).noExtensions()); - else if ("false".equals(s)) + } else if ("false".equals(s)) { result.add(new IntegerType(0).noExtensions()); + } return result; } private List funcIsInteger(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof IntegerType) + } else if (focus.get(0) instanceof IntegerType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof BooleanType) + } 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) { result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); - else + } else { result.add(new BooleanType(false).noExtensions()); + } return result; } private List funcIsBoolean(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof IntegerType) + } else if (focus.get(0) instanceof IntegerType) { result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); - else if (focus.get(0) instanceof DecimalType) + } 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()); - else if (focus.get(0) instanceof BooleanType) + } 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) { result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); - else + } else { result.add(new BooleanType(false).noExtensions()); + } return result; } private List funcIsDateTime(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) + } 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) + } 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 + ("([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 exp) throws FHIRException { - if (hostServices == null) - throw new FHIRException("Unable to check conformsTo - no hostservices provided"); + private List funcIsDate(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else { - String url = convertToString(execute(context, focus, exp.getParameters().get(0), true)); + } 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(false).noExtensions()); + } + return result; + } + + private List funcConformsTo(ExecutionContext context, List focus, ExpressionNode expr) throws FHIRException { + if (hostServices == null) { + throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo"); + } + List result = new ArrayList(); + if (focus.size() != 1) { + result.add(new BooleanType(false).noExtensions()); + } else { + String url = convertToString(execute(context, focus, expr.getParameters().get(0), true)); result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo, focus.get(0), url)).noExtensions()); } return result; @@ -3820,101 +5287,110 @@ public class FHIRPathEngine { private List funcIsTime(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof TimeType) + } else if (focus.get(0) instanceof TimeType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof StringType) + } 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()); - else + ("(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 funcIsString(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) + } 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; } private List funcIsQuantity(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() != 1) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof IntegerType) + } else if (focus.get(0) instanceof IntegerType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof DecimalType) + } else if (focus.get(0) instanceof DecimalType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof Quantity) + } else if (focus.get(0) instanceof Quantity) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof BooleanType) + } 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 + } else { result.add(new BooleanType(false).noExtensions()); + } return result; } public Quantity parseQuantityString(String s) { - if (s == null) + if (s == null) { return null; + } s = s.trim(); if (s.contains(" ")) { String v = s.substring(0, s.indexOf(" ")).trim(); s = s.substring(s.indexOf(" ")).trim(); - if (!Utilities.isDecimal(v, false)) + if (!Utilities.isDecimal(v, false)) { return null; - if (s.startsWith("'") && s.endsWith("'")) + } + if (s.startsWith("'") && s.endsWith("'")) { return Quantity.fromUcum(v, s.substring(1, s.length()-1)); - if (s.equals("year") || s.equals("years")) + } + if (s.equals("year") || s.equals("years")) { return Quantity.fromUcum(v, "a"); - else if (s.equals("month") || s.equals("months")) - return Quantity.fromUcum(v, "mo"); - else if (s.equals("week") || s.equals("weeks")) + } else if (s.equals("month") || s.equals("months")) { + return Quantity.fromUcum(v, "mo_s"); + } else if (s.equals("week") || s.equals("weeks")) { return Quantity.fromUcum(v, "wk"); - else if (s.equals("day") || s.equals("days")) + } else if (s.equals("day") || s.equals("days")) { return Quantity.fromUcum(v, "d"); - else if (s.equals("hour") || s.equals("hours")) + } else if (s.equals("hour") || s.equals("hours")) { return Quantity.fromUcum(v, "h"); - else if (s.equals("minute") || s.equals("minutes")) + } else if (s.equals("minute") || s.equals("minutes")) { return Quantity.fromUcum(v, "min"); - else if (s.equals("second") || s.equals("seconds")) + } else if (s.equals("second") || s.equals("seconds")) { return Quantity.fromUcum(v, "s"); - else if (s.equals("millisecond") || s.equals("milliseconds")) + } else if (s.equals("millisecond") || s.equals("milliseconds")) { 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 + } 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) + if (focus.size() != 1) { result.add(new BooleanType(false).noExtensions()); - else if (focus.get(0) instanceof IntegerType) + } else if (focus.get(0) instanceof IntegerType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof BooleanType) + } else if (focus.get(0) instanceof BooleanType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof DecimalType) + } else if (focus.get(0) instanceof DecimalType) { result.add(new BooleanType(true).noExtensions()); - else if (focus.get(0) instanceof StringType) + } else if (focus.get(0) instanceof StringType) { result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); - else + } else { result.add(new BooleanType(false).noExtensions()); + } return result; } @@ -3929,42 +5405,47 @@ public class FHIRPathEngine { int i1 = Integer.parseInt(n1.get(0).primitiveValue()); List result = new ArrayList(); - for (int i = i1; i < focus.size(); i++) + for (int i = i1; i < focus.size(); i++) { result.add(focus.get(i)); + } return result; } private List funcTail(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - for (int i = 1; i < focus.size(); i++) + 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) + if (focus.size() > 0) { result.add(focus.get(focus.size()-1)); + } return result; } private List funcFirst(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - if (focus.size() > 0) + if (focus.size() > 0) { result.add(focus.get(0)); + } return result; } - private List funcWhere(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + private List funcWhere(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List pc = new ArrayList(); for (Base item : focus) { pc.clear(); pc.add(item); Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); - if (v == Equality.True) + if (v == Equality.True) { result.add(item); + } } return result; } @@ -3972,34 +5453,38 @@ public class FHIRPathEngine { private List funcSelect(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List pc = new ArrayList(); + int i = 0; for (Base item : focus) { pc.clear(); pc.add(item); - result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); + result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true)); + i++; } return result; } - private List funcItem(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { + 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()) + if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) { result.add(focus.get(Integer.parseInt(s))); + } return result; } private List funcEmpty(ExecutionContext context, List focus, ExpressionNode exp) { List result = new ArrayList(); - result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); + result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); return result; } private List funcNot(ExecutionContext context, List focus, ExpressionNode exp) throws PathEngineException { List result = new ArrayList(); Equality v = asBool(focus, exp); - if (v != Equality.Null) + if (v != Equality.Null) { result.add(new BooleanType(v != Equality.True)); + } return result; } @@ -4021,143 +5506,169 @@ public class FHIRPathEngine { } private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr) throws PathEngineException, DefinitionException { - if (Utilities.noString(type)) - throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName", expr.getStart(), expr.toString()); - if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) + 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)) + } + if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { return; - + } + if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { getSimpleTypeChildTypesByName(name, result); } else if (type.equals(TypeDetails.FP_ClassInfo)) { getClassInfoChildTypesByName(name, result); } else { - String url = null; - if (type.contains("#")) { - url = type.substring(0, type.indexOf("#")); - } else { - url = type; - } - String tail = ""; - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); - if (sd == null) - throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong - List sdl = new ArrayList(); - ElementDefinitionMatch m = null; - if (type.contains("#")) - m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr); - if (m != null && hasDataType(m.definition)) { - if (m.fixedType != null) - { - StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs())); - if (dt == null) - throw new DefinitionException("unknown data type "+m.fixedType); - sdl.add(dt); - } else - for (TypeRefComponent t : m.definition.getType()) { - StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs())); - if (dt == null) - throw new DefinitionException("unknown data type "+t.getCode()); - sdl.add(dt); - } - } else { - sdl.add(sd); + String url = null; if (type.contains("#")) { - tail = type.substring(type.indexOf("#")+1); - tail = tail.substring(tail.indexOf(".")); + url = type.substring(0, type.indexOf("#")); + } else { + url = type; + } + String tail = ""; + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName"); + } + List sdl = new ArrayList(); + ElementDefinitionMatch m = null; + if (type.contains("#")) + m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr); + if (m != null && hasDataType(m.definition)) { + if (m.fixedType != null) { + StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null)); + if (dt == null) { + throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName"); + } + sdl.add(dt); + } else + for (TypeRefComponent t : m.definition.getType()) { + 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"); + } + addTypeAndDescendents(sdl, dt, worker.allStructures()); + // also add any descendant types + } + } else { + addTypeAndDescendents(sdl, sd, worker.allStructures()); + if (type.contains("#")) { + tail = type.substring(type.indexOf("#")+1); + tail = tail.substring(tail.indexOf(".")); + } } - } - for (StructureDefinition sdi : sdl) { - String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; - if (name.equals("**")) { - 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 (t.getCode().equals("Resource")) { - for (String rn : worker.getResourceNames()) { - if (!result.hasType(worker, rn)) { - getChildTypesByName(result.addType(rn), "**", result, expr); - } + for (StructureDefinition sdi : sdl) { + String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; + if (name.equals("**")) { + 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(); } - } else if (!result.hasType(worker, tn)) { - getChildTypesByName(result.addType(tn), "**", result, expr); + if (t.getCode().equals("Resource")) { + for (String rn : worker.getResourceNames()) { + if (!result.hasType(worker, rn)) { + getChildTypesByName(result.addType(rn), "**", result, expr); + } + } + } else if (!result.hasType(worker, tn)) { + getChildTypesByName(result.addType(tn), "**", result, expr); + } + } + } + } + } else if (name.equals("*")) { + 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 + result.addType("System.string"); + } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { + result.addType(sdi.getType()+"#"+ed.getPath()); + } else if (t.getCode().equals("Resource")) { + result.addTypes(worker.getResourceNames()); + } else { + result.addType(t.getCode()); + } + } + } + } else { + path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; + + ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); + if (ed != null) { + if (!Utilities.noString(ed.getFixedType())) + result.addType(ed.getFixedType()); + 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"); + } + 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); + } else if (t.getCode().equals("Resource")) { + result.addTypes(worker.getResourceNames()); + } else { + pt = new ProfiledType(t.getCode()); + } + if (pt != null) { + if (t.hasProfile()) { + pt.addProfiles(t.getProfile()); + } + if (ed.getDefinition().hasBinding()) { + pt.addBinding(ed.getDefinition().getBinding()); + } + result.addType(pt); } } } - } - } else if (name.equals("*")) { - 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 - result.addType("System.string"); - else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) - result.addType(sdi.getType()+"#"+ed.getPath()); - else if (t.getCode().equals("Resource")) - result.addTypes(worker.getResourceNames()); - else - result.addType(t.getCode()); - } - } - } else { - path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; - - ElementDefinitionMatch ed = getElementDefinition(sdi, path, false, expr); - if (ed != null) { - if (!Utilities.noString(ed.getFixedType())) - result.addType(ed.getFixedType()); - 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"); - 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); - else if (t.getCode().equals("Resource")) - result.addTypes(worker.getResourceNames()); - else - pt = new ProfiledType(t.getCode()); - if (pt != null) { - if (t.hasProfile()) - pt.addProfiles(t.getProfile()); - if (ed.getDefinition().hasBinding()) - pt.addBinding(ed.getDefinition().getBinding()); - result.addType(pt); - } - } + } } } } - } + } + + 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")) + if (name.equals("namespace")) { result.addType(TypeDetails.FP_String); - if (name.equals("name")) + } + if (name.equals("name")) { result.addType(TypeDetails.FP_String); + } } private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { - if (name.equals("namespace")) + if (name.equals("namespace")) { result.addType(TypeDetails.FP_String); - if (name.equals("name")) + } + if (name.equals("name")) { result.addType(TypeDetails.FP_String); + } } @@ -4166,25 +5677,30 @@ public class FHIRPathEngine { if (ed.getPath().equals(path)) { if (ed.hasContentReference()) { return getElementDefinitionById(sd, ed.getContentReference()); - } else + } else { 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) + 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 (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)) + String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); + if (primitiveTypes.contains(s)) { return new ElementDefinitionMatch(ed, s); - else - return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); + } else { + return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); + } } 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 PathEngineException("Internal typing issue....", expr.getStart(), expr.toString()); - StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); - if (nsd == null) - throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode(), expr.getStart(), expr.toString()); + if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this + throw new Error("Internal typing issue...."); + } + 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"); + } return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); } if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { @@ -4196,14 +5712,15 @@ public class FHIRPathEngine { } private boolean isAbstractType(List list) { - return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); -} - + return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); + } private boolean hasType(ElementDefinition ed, String s) { - for (TypeRefComponent t : ed.getType()) - if (s.equalsIgnoreCase(t.getCode())) + for (TypeRefComponent t : ed.getType()) { + if (s.equalsIgnoreCase(t.getCode())) { return true; + } + } return false; } @@ -4213,8 +5730,9 @@ public class FHIRPathEngine { private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { for (ElementDefinition ed : sd.getSnapshot().getElement()) { - if (ref.equals("#"+ed.getId())) + if (ref.equals("#"+ed.getId())) { return new ElementDefinitionMatch(ed, null); + } } return null; } @@ -4226,8 +5744,9 @@ public class FHIRPathEngine { public String takeLog() { - if (!hasLog()) + if (!hasLog()) { return ""; + } String s = log.toString(); log = new StringBuilder(); return s; @@ -4244,119 +5763,174 @@ public class FHIRPathEngine { * @throws PathEngineException * @throws DefinitionException */ - public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException { + public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException { StructureDefinition sd = profile; - ElementDefinition focus = null; + TypedElementDefinition focus = null; + boolean okToNotResolve = false; if (expr.getKind() == Kind.Name) { - if (element.hasSlicing()) { - ElementDefinition slice = pickMandatorySlice(sd, element); - if (slice == null) - throw new DefinitionException("Error in discriminator at "+element.getId()+": found a sliced element while resolving the fixed value for one of the slices"); - element = slice; + 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()); + } + element = new TypedElementDefinition(slice); } - + if (expr.getName().equals("$this")) { focus = element; } else { - List childDefinitions; - childDefinitions = ProfileUtilities.getChildMap(sd, element); - // if that's empty, get the children of the type - if (childDefinitions.isEmpty()) { - sd = fetchStructureByType(element); - if (sd == null) - throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getProfile()+"' on "+element.getId()+" could not be resolved"); - childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); - } - for (ElementDefinition t : childDefinitions) { - if (tailMatches(t, expr.getName())) { - focus = t; - break; + List childDefinitions; + childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); + // if that's empty, get the children of the type + if (childDefinitions.isEmpty()) { + + sd = fetchStructureByType(element, expr); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_THIS_CANNOT_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) + focus = new TypedElementDefinition(t); + break; + } } - } } } else if (expr.getKind() == Kind.Function) { if ("resolve".equals(expr.getName())) { - if (!element.hasType()) - throw new DefinitionException("illegal use of resolve() in discriminator - no type on element "+element.getId()); - if (element.getType().size() > 1) - throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on "+element.getId()); - if (!element.getType().get(0).hasTarget()) - throw new DefinitionException("illegal use of resolve() in discriminator - type on "+element.getId()+" is not Reference ("+element.getType().get(0).getCode()+")"); - if (element.getType().get(0).getTargetProfile().size() > 1) - throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on "+element.getId()); - sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue()); - if (sd == null) - throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getTargetProfile()+"' on "+element.getId()+" could not be resolved"); - focus = sd.getSnapshot().getElementFirstRep(); + if (element.getTypes().size() == 0) { + 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()); + } + if (!element.getTypes().get(0).hasTarget()) { + 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()); + } + sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue()); + if (sd == null) { + 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())) { String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); - List childDefinitions = ProfileUtilities.getChildMap(sd, element); + 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()) ? - 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()); - if (exsd.getUrl().equals(targetUrl)) { - if (ProfileUtilities.getChildMap(sd, t).isEmpty()) - sd = exsd; - focus = t; - break; - } + System.out.println("t: "+t.getId()); + 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()); + } + if (exsd != null && exsd.getUrl().equals(targetUrl)) { + if (profileUtilities.getChildMap(sd, t).isEmpty()) { + sd = exsd; + } + focus = new TypedElementDefinition(t); + break; + } } } - } else - throw new DefinitionException("illegal function name "+expr.getName()+"() in discriminator"); + 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()) { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId()); + } + List atn = new ArrayList<>(); + for (TypeRefComponent tr : element.getTypes()) { + if (!tr.hasCode()) { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId()); + } + atn.add(tr.getCode()); + } + 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); + } else { + focus = element; + } + } + } else { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName()); + } } else if (expr.getKind() == Kind.Group) { - throw new DefinitionException("illegal expression syntax in discriminator (group)"); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString()); } else if (expr.getKind() == Kind.Constant) { - throw new DefinitionException("illegal expression syntax in discriminator (const)"); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); } - if (focus == null) - throw new DefinitionException("Unable to resolve discriminator in definitions: "+expr.toString()); - else if (expr.getInner() == null) - return focus; - else { - return evaluateDefinition(expr.getInner(), sd, focus); - } + 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()); + } + } 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 + // 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")) { + next = next.getInner(); + } + if (next == null) { + return focus; + } else { + return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences); + } + } } private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException { - List list = ProfileUtilities.getSliceList(sd, element); + List list = profileUtilities.getSliceList(sd, element); for (ElementDefinition ed : list) { - if (ed.getMin() > 0) + if (ed.getMin() > 0) { return ed; + } } return null; } - private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException { - if (ed.getType().size() == 0) - throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, no type"); - if (ed.getType().size() > 1) - throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple types"); - if (ed.getType().get(0).getProfile().size() > 1) - throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple type profiles"); - if (ed.getType().get(0).hasProfile()) - return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); - else - return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); + 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()); + } + 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()); + } + if (ed.getTypes().get(0).hasProfile()) { + return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue()); + } else { + return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null)); + } } private boolean tailMatches(ElementDefinition t, String d) { String tail = tailDot(t.getPath()); - if (d.contains("[")) + if (d.contains("[")) { return tail.startsWith(d.substring(0, d.indexOf('['))); - else if (tail.equals(d)) + } 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)) + } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) { return true; + } return false; } @@ -4365,14 +5939,17 @@ public class FHIRPathEngine { } private Equality asBool(List items, ExpressionNode expr) throws PathEngineException { - if (items.size() == 0) + if (items.size() == 0) { return Equality.Null; - else if (items.size() == 1) - return asBool(items.get(0)); - else - throw new PathEngineException("Unable to evaluate as a boolean: "+convertToString(items), expr.getStart(), expr.toString()); + } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { + return asBool(items.get(0), true); + } else if (items.size() == 1) { + return Equality.True; + } else { + throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items)); + } } - + private Equality asBoolFromInt(String s) { try { int i = Integer.parseInt(s); @@ -4389,52 +5966,57 @@ 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 + } else { return Equality.Null; + } } catch (Exception e) { return Equality.Null; } } - private Equality asBool(Base item) { - if (item instanceof BooleanType) + private Equality asBool(Base item, boolean narrow) { + if (item instanceof BooleanType) { return boolToTriState(((BooleanType) item).booleanValue()); - else if (item.isBooleanPrimitive()) { - if (Utilities.existsInList(item.primitiveValue(), "true")) + } else if (item.isBooleanPrimitive()) { + if (Utilities.existsInList(item.primitiveValue(), "true")) { return Equality.True; - else if (Utilities.existsInList(item.primitiveValue(), "false")) + } else if (Utilities.existsInList(item.primitiveValue(), "false")) { return Equality.False; - else + } else { return Equality.Null; - } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) + } + } else if (narrow) { + return Equality.False; + } 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")) + } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) { return asBoolFromDec(item.primitiveValue()); - else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { - if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) + } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { + if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) { return Equality.True; - else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) + } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) { return Equality.False; - else if (Utilities.isInteger(item.primitiveValue())) + } else if (Utilities.isInteger(item.primitiveValue())) { return asBoolFromInt(item.primitiveValue()); - else if (Utilities.isDecimal(item.primitiveValue(), true)) + } else if (Utilities.isDecimal(item.primitiveValue(), true)) { return asBoolFromDec(item.primitiveValue()); - else + } else { return Equality.Null; + } } - return Equality.Null; + return Equality.Null; } - + private Equality boolToTriState(boolean b) { return b ? Equality.True : Equality.False; } - public TerminologyServiceOptions getTerminologyServiceOptions() { + public ValidationOptions getTerminologyServiceOptions() { return terminologyServiceOptions; } @@ -4442,5 +6024,22 @@ public class FHIRPathEngine { public IWorkerContext getWorker() { return worker; } - -} \ No newline at end of file + + public boolean isAllowPolymorphicNames() { + return allowPolymorphicNames; + } + + public void setAllowPolymorphicNames(boolean allowPolymorphicNames) { + this.allowPolymorphicNames = allowPolymorphicNames; + } + + public boolean isLiquidMode() { + return liquidMode; + } + + public void setLiquidMode(boolean liquidMode) { + this.liquidMode = liquidMode; + } + + +} 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 63deeda3f..d4bc80603 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 @@ -410,11 +410,11 @@ public class LiquidEngine implements IEvaluationContext { } @Override - public Base resolveReference(Object appContext, String url) throws FHIRException { + public Base resolveReference(Object appContext, String url, Base base) throws FHIRException { if (externalHostServices == null) return null; LiquidEngineContext ctxt = (LiquidEngineContext) appContext; - return resolveReference(ctxt.externalContext, url); + return resolveReference(ctxt.externalContext, url, base); } @Override 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 14835b989..6914e851f 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 @@ -212,7 +212,7 @@ public class StructureMapUtilities { } @Override - public Base resolveReference(Object appContext, String url) throws FHIRException { + public Base resolveReference(Object appContext, String url, Base base) throws FHIRException { if (services == null) return null; return services.resolveReference(appContext, url); 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 6dd98e693..626a34e33 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 @@ -1,9 +1,17 @@ package org.hl7.fhir.r4.test; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.*; +import java.util.stream.Stream; + +import javax.xml.parsers.ParserConfigurationException; + import org.apache.commons.lang3.NotImplementedException; import org.fhir.ucum.UcumException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.r4.formats.JsonParser; import org.hl7.fhir.r4.formats.XmlParser; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.test.utils.TestingUtilities; @@ -11,7 +19,10 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine; import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xml.XMLUtil; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -20,18 +31,14 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.*; -import java.util.stream.Stream; - import static org.junit.jupiter.api.Assertions.assertEquals; - +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class FHIRPathTests { + public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION} + public class FHIRPathTestEvaluationServices implements IEvaluationContext { @Override @@ -41,7 +48,6 @@ public class FHIRPathTests { @Override public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { - throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstantType), when item is element"); } @@ -66,7 +72,7 @@ public class FHIRPathTests { } @Override - public Base resolveReference(Object appContext, String url) throws FHIRException { + public Base resolveReference(Object appContext, String url, Base base) throws FHIRException { throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveReference), when item is element"); } @@ -84,29 +90,33 @@ public class FHIRPathTests { public ValueSet resolveValueSet(Object appContext, String url) { return TestingUtilities.context().fetchResource(ValueSet.class, url); } + } private static FHIRPathEngine fp; + private final Map resources = new HashMap(); @BeforeAll - public static void setup() { + public static void setUp() { fp = new FHIRPathEngine(TestingUtilities.context()); } public static Stream data() throws ParserConfigurationException, SAXException, IOException { - Document dom = XMLUtil.parseFileToDom(TestingUtilities.resourceNameToFile("fhirpath", "tests-fhir-r4.xml")); + Document dom = XMLUtil.parseToDom(TestingUtilities.loadTestResource("r4", "fhirpath", "tests-fhir-r4.xml")); List list = new ArrayList(); List groups = new ArrayList(); XMLUtil.getNamedChildren(dom.getDocumentElement(), "group", groups); for (Element g : groups) { XMLUtil.getNamedChildren(g, "test", list); + XMLUtil.getNamedChildren(g, "modeTest", list); } - List objects = new ArrayList(); + List objects = new ArrayList<>(); for (Element e : list) { objects.add(Arguments.of(getName(e), e)); } + return objects.stream(); } @@ -116,95 +126,144 @@ public class FHIRPathTests { int ndx = 0; for (int i = 0; i < p.getChildNodes().getLength(); i++) { Node c = p.getChildNodes().item(i); - if (c == e) + if (c == e) { break; - else if (c instanceof Element) + } else if (c instanceof Element) { ndx++; + } } - if (Utilities.noString(s)) + if (Utilities.noString(s)) { s = "?? - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]"; - else + } else { s = s + " - G " + p.getAttribute("name") + "[" + Integer.toString(ndx + 1) + "]"; + } return s; } - - private Map resources = new HashMap(); - - @SuppressWarnings("deprecation") - @Disabled @ParameterizedTest(name = "{index}: file {0}") @MethodSource("data") public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException { + // Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with + // all my time based tests failing locally... + TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100")); fp.setHostServices(new FHIRPathTestEvaluationServices()); String input = test.getAttribute("inputfile"); String expression = XMLUtil.getNamedChild(test, "expression").getTextContent(); - boolean fail = Utilities.existsInList(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"), "true", "semantic"); + TestResultType fail = TestResultType.OK; + if ("syntax".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) { + fail = TestResultType.SYNTAX; + } else if ("semantic".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) { + fail = TestResultType.SEMANTICS; + } else if ("execution".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) { + fail = TestResultType.EXECUTION; + }; Resource res = null; List outcome = new ArrayList(); - ExpressionNode node = fp.parse(expression); + System.out.println(name); + + ExpressionNode node = null; try { - if (Utilities.noString(input)) - fp.check(null, null, node); - else { + node = fp.parse(expression); + Assertions.assertTrue(fail != TestResultType.SYNTAX, String.format("Expected exception didn't occur parsing %s", expression)); + } catch (Exception e) { + System.out.println("Parsing Error: "+e.getMessage()); + Assertions.assertTrue(fail == TestResultType.SYNTAX, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression)); + } + + if (node != null) { + if (!Utilities.noString(input)) { res = resources.get(input); if (res == null) { - res = new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile(input))); - resources.put(input, res); - } - fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node); - } - outcome = fp.evaluate(res, node); - Assertions.assertFalse(fail, String.format("Expected exception parsing %s", expression)); - } catch (Exception e) { - Assertions.assertTrue(fail, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression)); - } - - if ("true".equals(test.getAttribute("predicate"))) { - boolean ok = fp.convertToBoolean(outcome); - outcome.clear(); - outcome.add(new BooleanType(ok)); - } - if (fp.hasLog()) - System.out.println(fp.takeLog()); - - List expected = new ArrayList(); - XMLUtil.getNamedChildren(test, "output", expected); - Assertions.assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression)); - if ("false".equals(test.getAttribute("ordered"))) { - for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) { - String tn = outcome.get(i).fhirType(); - String s; - if (outcome.get(i) instanceof Quantity) - s = fp.convertToString(outcome.get(i)); - else - s = ((PrimitiveType) outcome.get(i)).asStringValue(); - boolean found = false; - for (Element e : expected) { - if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) && - (Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) - found = true; - } - Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression)); - } - } else { - for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) { - String tn = expected.get(i).getAttribute("type"); - if (!Utilities.noString(tn)) { - Assertions.assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType())); - } - String v = expected.get(i).getTextContent(); - if (!Utilities.noString(v)) { - if (outcome.get(i) instanceof Quantity) { - Quantity q = fp.parseQuantityString(v); - Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString())); + if (input.endsWith(".json")) { + res = new JsonParser().parse(TestingUtilities.loadTestResource("r4", input)); } else { - Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType())); - Assertions.assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression)); + res = new XmlParser().parse(TestingUtilities.loadTestResource("r4", input)); + } + resources.put(input, res); + } + } + + try { + if (Utilities.noString(input)) { + fp.check(null, null, node); + } else { + fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node); + } + Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression)); + } catch (Exception e) { + System.out.println("Checking Error: "+e.getMessage()); + Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression)); + node = null; + } + } + + if (node != null) { + try { + outcome = fp.evaluate(res, node); + Assertions.assertTrue(fail == TestResultType.OK, String.format("Expected exception didn't occur executing %s", expression)); + } catch (Exception e) { + System.out.println("Execution Error: "+e.getMessage()); + Assertions.assertTrue(fail == TestResultType.EXECUTION, String.format("Unexpected exception executing %s: " + e.getMessage(), expression)); + node = null; + } + } + + if (fp.hasLog()) { + System.out.println(name); + System.out.println(fp.takeLog()); + } + + if (node != null) { + if ("true".equals(test.getAttribute("predicate"))) { + boolean ok = fp.convertToBoolean(outcome); + outcome.clear(); + outcome.add(new BooleanType(ok)); + } + + List expected = new ArrayList(); + XMLUtil.getNamedChildren(test, "output", expected); + assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression)); + if ("false".equals(test.getAttribute("ordered"))) { + for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) { + String tn = outcome.get(i).fhirType(); + String s; + if (outcome.get(i) instanceof Quantity) { + s = fp.convertToString(outcome.get(i)); + } else { + s = ((PrimitiveType) outcome.get(i)).asStringValue(); + } + boolean found = false; + for (Element e : expected) { + if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) && + (Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) { + found = true; + } + } + Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression)); + } + } else { + for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) { + String tn = expected.get(i).getAttribute("type"); + if (!Utilities.noString(tn)) { + assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType())); + } + String v = expected.get(i).getTextContent(); + if (!Utilities.noString(v)) { + if (outcome.get(i) instanceof Quantity) { + Quantity q = fp.parseQuantityString(v); + Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString())); + } else { + Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType())); + if (!(v.equals(((PrimitiveType) outcome.get(i)).fpValue()))) { + System.out.println(name); + System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression)); + } + assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression)); + } } } } 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 d646bcd4e..c3badb9cf 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 @@ -331,7 +331,7 @@ public class SnapShotGenerationTests { } @Override - public Base resolveReference(Object appContext, String url) { + public Base resolveReference(Object appContext, String url, Base base) { // TODO Auto-generated method stub return null; } From 90f7214195cbda1e7f79e9cff660f1ee885f7934 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 10 Jan 2023 11:34:41 +1100 Subject: [PATCH 2/9] test NPE in markdown processing --- .../conformance/profile/ProfileUtilities.java | 8 +++--- .../org/hl7/fhir/r5/utils/MarkdownTests.java | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/MarkdownTests.java 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 eefa9d3d8..25a19cc83 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 @@ -1703,24 +1703,24 @@ public class ProfileUtilities extends TranslatingUtilities { private static boolean isLikelySourceURLReference(String url, List resourceNames, Set baseFilenames, Set localFilenames) { if (resourceNames != null) { for (String n : resourceNames) { - if (url.startsWith(n.toLowerCase()+".html")) { + if (n != null && url.startsWith(n.toLowerCase()+".html")) { return true; } - if (url.startsWith(n.toLowerCase()+"-definitions.html")) { + if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) { return true; } } } if (localFilenames != null) { for (String n : localFilenames) { - if (url.startsWith(n.toLowerCase())) { + if (n != null && url.startsWith(n.toLowerCase())) { return false; } } } if (baseFilenames != null) { for (String n : baseFilenames) { - if (url.startsWith(n.toLowerCase())) { + if (n != null && url.startsWith(n.toLowerCase())) { return true; } } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/MarkdownTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/MarkdownTests.java new file mode 100644 index 000000000..e61304981 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/MarkdownTests.java @@ -0,0 +1,26 @@ +package org.hl7.fhir.r5.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; + +import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; +import org.hl7.fhir.r5.context.ContextUtilities; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class MarkdownTests { + + @Test + void test() { + IWorkerContext ctxt = TestingUtilities.getSharedWorkerContext(); + ContextUtilities cu = new ContextUtilities(ctxt); + ProfileUtilities pu = new ProfileUtilities(ctxt, null, cu); + String s = pu.processRelativeUrls("UDI may identify an unique instance of a device, or it may only identify the type of the device. See [UDI mappings](device-mappings.html#udi) for a complete mapping of UDI parts to Device.", + "http://hl7.org/fhir", "", cu.getTypeNames(), null, null, true); + Assertions.assertNotNull(s); + } +} \ No newline at end of file From 20d2f582be78d832cbfe392d0613e2297a5ff497 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 11 Jan 2023 23:12:41 +1100 Subject: [PATCH 3/9] go-publish support --- .../org/hl7/fhir/utilities/FTPClient.java | 2 +- .../hl7/fhir/utilities/npm/PackageList.java | 5 ++++- .../fhir/utilities/npm/PackageListTests.java | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/PackageListTests.java diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java index 3b91f3af2..b7f0bd4af 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java @@ -75,7 +75,7 @@ public class FTPClient { } private String resolveRemotePath(String path) { - return String.join("", this.path, path); + return String.join("/", this.path, path); } /** diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageList.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageList.java index 3e2cffd7b..db822b265 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageList.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageList.java @@ -104,7 +104,9 @@ public class PackageList { json.set("path", path); json.set("status", status); json.set("sequence", sequence); - json.set("fhirversion", fhirVersion.toCode()); + if (fhirVersion != null) { + json.set("fhirversion", fhirVersion.toCode()); + } } public void describe(String desc, String descMD, String changes) { @@ -239,6 +241,7 @@ public class PackageList { json.getJsonArray("list").remove(cibuild.json); } cibuild = new PackageListEntry(new JsonObject()); + cibuild.init(version, path, status, status, null); json.getJsonArray("list").add(0, cibuild.json); } diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/PackageListTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/PackageListTests.java new file mode 100644 index 000000000..a1ef263fe --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/PackageListTests.java @@ -0,0 +1,22 @@ +package org.hl7.fhir.utilities.npm; + +import java.net.URISyntaxException; + +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.utilities.json.parser.JsonParser; +import org.hl7.fhir.utilities.tests.BaseTestingUtilities; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class PackageListTests { + + @Test + @DisplayName("test reading package list") + public void getPackageInfoFromJSONTest() throws java.io.IOException, URISyntaxException { + PackageList pl = new PackageList(JsonParser.parseObject(BaseTestingUtilities.loadTestResource("npm", "package-new.json"))); + Assertions.assertNotNull(pl.ciBuild()); + + } + +} From a06803d936998a5f80e3353f4b1f02623123dbd8 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 14 Jan 2023 08:45:13 +1100 Subject: [PATCH 4/9] Start implementing loading structure maps with validation --- .../fhir/r5/utils/structuremap/StructureMapUtilities.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index 3ef5835e4..bde738d20 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -69,10 +69,12 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.*; @@ -2599,4 +2601,8 @@ public class StructureMapUtilities { this.terminologyServiceOptions = terminologyServiceOptions; } + public Element parseForValidation(ByteArrayInputStream byteArrayInputStream, String version, List errors) { + return null; + } + } \ No newline at end of file From f91aa8f3da6bb9105689dc78c3ffe2ca891b8d3b Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 14 Jan 2023 09:08:01 +1100 Subject: [PATCH 5/9] release notes --- RELEASE_NOTES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6301a0bf9..4f75e9488 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,10 +1,13 @@ ## Validator Changes -* no changes +* Better tracking and reporting of snapshot generation issues +* Update FHIRPath implementation to R4 with all corrections and extensions from R5 implemnetation ## Other code changes -* Better tracking and reporting of snapshot generation issues * Inclusion of SAXON dependency for processing XSLT * Error message spacing correction * Basic FTPClient implementation +* test NPE in markdown processing +* go-publish support +* Start implementing loading structure maps with validation From 434124d766d22a80b772a35a5f706e39683167bc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 14 Jan 2023 10:15:01 +1100 Subject: [PATCH 6/9] more work on FTP --- .../org/hl7/fhir/utilities/FTPClient.java | 8 ++++++-- .../org/hl7/fhir/utilities/FTPClientTest.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java index a44f6900f..69f712159 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FTPClient.java @@ -36,7 +36,11 @@ public class FTPClient { protected FTPClient(String server, int port, String path, String user, String password) { this.server = server; this.port = port; - this.path = path; + if (path.endsWith("/")) { + this.path = path; + } else { + this.path = path + "/"; + } this.user = user; this.password = password; @@ -77,7 +81,7 @@ public class FTPClient { } private String resolveRemotePath(String path) { - return String.join("/", this.path, path); + return String.join("", this.path, path); } /** diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FTPClientTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FTPClientTest.java index 7e2d97256..a8bba306b 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FTPClientTest.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FTPClientTest.java @@ -130,6 +130,25 @@ public class FTPClientTest implements ResourceLoaderTests { return client; } + @Test + public void testDelete2() throws IOException { + + FTPClient client = connectToFTPClient2(); + + String deleteFilePath = dummyFileToDeletePath.toFile().getAbsolutePath(); + assertTrue(fakeFtpServer.getFileSystem().exists(deleteFilePath)); + + client.delete( RELATIVE_PATH_2 + "/" + DUMMY_FILE_TO_DELETE); + assertFalse(fakeFtpServer.getFileSystem().exists(deleteFilePath)); + } + + @NotNull + private static FTPClient connectToFTPClient2() throws IOException { + FTPClient client = new FTPClient("localhost", FAKE_FTP_PORT, RELATIVE_PATH_1, DUMMY_USER, DUMMY_PASSWORD); + client.connect(); + return client; + } + @Test public void testUpload() throws IOException { From 6c2a488402281b11f88eef58bc83c5dd1ba01135 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 16 Jan 2023 10:12:59 +1100 Subject: [PATCH 7/9] fix type convertors to handle null --- .../main/java/org/hl7/fhir/r4/model/Base.java | 180 +++++++++++- .../org/hl7/fhir/r4b/model/TypeConvertor.java | 221 +++++++++++++- .../org/hl7/fhir/r5/model/TypeConvertor.java | 270 +++++++++++++++++- 3 files changed, 667 insertions(+), 4 deletions(-) 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 13f3c447a..fcdfc547a 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 @@ -319,6 +319,9 @@ private Map userData; // -- converters for property setters public Type castToType(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Type) return (Type) b; else if (b.isMetadataBased()) @@ -329,6 +332,9 @@ private Map userData; public BooleanType castToBoolean(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof BooleanType) return (BooleanType) b; else @@ -336,6 +342,9 @@ private Map userData; } public IntegerType castToInteger(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof IntegerType) return (IntegerType) b; else @@ -343,6 +352,9 @@ private Map userData; } public DecimalType castToDecimal(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DecimalType) return (DecimalType) b; else if (b.hasPrimitiveValue()) @@ -352,6 +364,9 @@ private Map userData; } public Base64BinaryType castToBase64Binary(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Base64BinaryType) return (Base64BinaryType) b; else @@ -359,6 +374,9 @@ private Map userData; } public InstantType castToInstant(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof InstantType) return (InstantType) b; else @@ -366,6 +384,9 @@ private Map userData; } public StringType castToString(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof StringType) return (StringType) b; else if (b.hasPrimitiveValue()) @@ -375,6 +396,9 @@ private Map userData; } public UriType castToUri(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UriType) return (UriType) b; else if (b.hasPrimitiveValue()) @@ -384,6 +408,9 @@ private Map userData; } public UrlType castToUrl(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UrlType) return (UrlType) b; else if (b.hasPrimitiveValue()) @@ -393,6 +420,9 @@ private Map userData; } public CanonicalType castToCanonical(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof CanonicalType) return (CanonicalType) b; else if (b.hasPrimitiveValue()) @@ -402,6 +432,9 @@ private Map userData; } public DateType castToDate(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DateType) return (DateType) b; else if (b.hasPrimitiveValue()) @@ -411,6 +444,9 @@ private Map userData; } public DateTimeType castToDateTime(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DateTimeType) return (DateTimeType) b; else if (b.fhirType().equals("dateTime")) @@ -420,6 +456,9 @@ private Map userData; } public TimeType castToTime(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof TimeType) return (TimeType) b; else @@ -427,6 +466,9 @@ private Map userData; } public CodeType castToCode(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof CodeType) return (CodeType) b; else if (b.isPrimitive()) @@ -436,6 +478,9 @@ private Map userData; } public OidType castToOid(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof OidType) return (OidType) b; else @@ -443,6 +488,9 @@ private Map userData; } public IdType castToId(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof IdType) return (IdType) b; else @@ -450,6 +498,9 @@ private Map userData; } public UnsignedIntType castToUnsignedInt(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UnsignedIntType) return (UnsignedIntType) b; else @@ -457,6 +508,9 @@ private Map userData; } public PositiveIntType castToPositiveInt(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof PositiveIntType) return (PositiveIntType) b; else @@ -464,6 +518,9 @@ private Map userData; } public MarkdownType castToMarkdown(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof MarkdownType) return (MarkdownType) b; else if (b.hasPrimitiveValue()) @@ -473,6 +530,9 @@ private Map userData; } public Annotation castToAnnotation(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Annotation) return (Annotation) b; else @@ -480,13 +540,20 @@ private Map userData; } public Dosage castToDosage(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Dosage) return (Dosage) b; - else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); + else + throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); } public Attachment castToAttachment(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Attachment) return (Attachment) b; else @@ -494,6 +561,9 @@ private Map userData; } public Identifier castToIdentifier(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Identifier) return (Identifier) b; else @@ -501,6 +571,9 @@ private Map userData; } public CodeableConcept castToCodeableConcept(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof CodeableConcept) return (CodeableConcept) b; else if (b instanceof Element) { @@ -514,6 +587,9 @@ private Map userData; } public Population castToPopulation(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Population) return (Population) b; else @@ -522,6 +598,9 @@ private Map userData; public Coding castToCoding(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Coding) return (Coding) b; else if (b instanceof Element) { @@ -537,6 +616,9 @@ private Map userData; } public Quantity castToQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Quantity) return (Quantity) b; else @@ -544,6 +626,9 @@ private Map userData; } public Money castToMoney(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Money) return (Money) b; else @@ -551,6 +636,9 @@ private Map userData; } public Duration castToDuration(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Duration) return (Duration) b; else @@ -558,6 +646,9 @@ private Map userData; } public SimpleQuantity castToSimpleQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof SimpleQuantity) return (SimpleQuantity) b; else if (b instanceof Quantity) { @@ -574,6 +665,9 @@ private Map userData; } public Range castToRange(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Range) return (Range) b; else @@ -581,6 +675,9 @@ private Map userData; } public Period castToPeriod(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Period) return (Period) b; else @@ -588,6 +685,9 @@ private Map userData; } public Ratio castToRatio(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Ratio) return (Ratio) b; else @@ -595,6 +695,9 @@ private Map userData; } public SampledData castToSampledData(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof SampledData) return (SampledData) b; else @@ -602,6 +705,9 @@ private Map userData; } public Signature castToSignature(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Signature) return (Signature) b; else @@ -609,6 +715,9 @@ private Map userData; } public HumanName castToHumanName(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof HumanName) return (HumanName) b; else @@ -616,6 +725,9 @@ private Map userData; } public Address castToAddress(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Address) return (Address) b; else @@ -623,6 +735,9 @@ private Map userData; } public ContactDetail castToContactDetail(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ContactDetail) return (ContactDetail) b; else @@ -630,6 +745,9 @@ private Map userData; } public Contributor castToContributor(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Contributor) return (Contributor) b; else @@ -637,6 +755,9 @@ private Map userData; } public UsageContext castToUsageContext(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UsageContext) return (UsageContext) b; else @@ -644,6 +765,9 @@ private Map userData; } public RelatedArtifact castToRelatedArtifact(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof RelatedArtifact) return (RelatedArtifact) b; else @@ -651,6 +775,9 @@ private Map userData; } public ContactPoint castToContactPoint(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ContactPoint) return (ContactPoint) b; else @@ -658,6 +785,9 @@ private Map userData; } public Timing castToTiming(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Timing) return (Timing) b; else @@ -665,6 +795,9 @@ private Map userData; } public Reference castToReference(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Reference) return (Reference) b; else if (b.isPrimitive() && Utilities.isURL(b.primitiveValue())) @@ -677,6 +810,9 @@ private Map userData; } public Meta castToMeta(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Meta) return (Meta) b; else @@ -685,6 +821,9 @@ private Map userData; public MarketingStatus castToMarketingStatus(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof MarketingStatus) return (MarketingStatus) b; else @@ -692,6 +831,9 @@ private Map userData; } public ProductShelfLife castToProductShelfLife(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ProductShelfLife) return (ProductShelfLife) b; else @@ -699,6 +841,9 @@ private Map userData; } public ProdCharacteristic castToProdCharacteristic(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ProdCharacteristic) return (ProdCharacteristic) b; else @@ -707,6 +852,9 @@ private Map userData; public SubstanceAmount castToSubstanceAmount(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof SubstanceAmount) return (SubstanceAmount) b; else @@ -714,6 +862,9 @@ private Map userData; } public Extension castToExtension(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Extension) return (Extension) b; else @@ -721,6 +872,9 @@ private Map userData; } public Resource castToResource(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Resource) return (Resource) b; else @@ -728,6 +882,9 @@ private Map userData; } public Narrative castToNarrative(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Narrative) return (Narrative) b; else @@ -736,6 +893,9 @@ private Map userData; public ElementDefinition castToElementDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ElementDefinition) return (ElementDefinition) b; else @@ -743,6 +903,9 @@ private Map userData; } public DataRequirement castToDataRequirement(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DataRequirement) return (DataRequirement) b; else @@ -750,6 +913,9 @@ private Map userData; } public Expression castToExpression(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Expression) return (Expression) b; else @@ -758,6 +924,9 @@ private Map userData; public ParameterDefinition castToParameterDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ParameterDefinition) return (ParameterDefinition) b; else @@ -765,6 +934,9 @@ private Map userData; } public TriggerDefinition castToTriggerDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof TriggerDefinition) return (TriggerDefinition) b; else @@ -772,6 +944,9 @@ private Map userData; } public XhtmlNode castToXhtml(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Element) { return ((Element) b).getXhtml(); } else if (b instanceof XhtmlType) { @@ -787,6 +962,9 @@ private Map userData; } public String castToXhtmlString(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Element) { return ((Element) b).getValue(); } else if (b instanceof XhtmlType) { diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/TypeConvertor.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/TypeConvertor.java index af44c2fa7..fe2a76457 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/TypeConvertor.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/TypeConvertor.java @@ -15,6 +15,10 @@ public class TypeConvertor { // -- converters for property setters public static DataType castToType(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DataType) return (DataType) b; else if (b.isMetadataBased()) @@ -25,6 +29,10 @@ public class TypeConvertor { public static BooleanType castToBoolean(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof BooleanType) return (BooleanType) b; else @@ -32,6 +40,10 @@ public class TypeConvertor { } public static IntegerType castToInteger(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof IntegerType) return (IntegerType) b; else @@ -39,6 +51,10 @@ public class TypeConvertor { } public static Integer64Type castToInteger64(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Integer64Type) return (Integer64Type) b; else @@ -46,6 +62,10 @@ public class TypeConvertor { } public static DecimalType castToDecimal(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DecimalType) return (DecimalType) b; else if (b.hasPrimitiveValue()) @@ -55,6 +75,10 @@ public class TypeConvertor { } public static Base64BinaryType castToBase64Binary(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Base64BinaryType) return (Base64BinaryType) b; else @@ -62,6 +86,10 @@ public class TypeConvertor { } public static InstantType castToInstant(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof InstantType) return (InstantType) b; else @@ -69,6 +97,10 @@ public class TypeConvertor { } public static StringType castToString(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof StringType) return (StringType) b; else if (b.hasPrimitiveValue()) @@ -78,6 +110,10 @@ public class TypeConvertor { } public static UriType castToUri(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UriType) return (UriType) b; else if (b.hasPrimitiveValue()) @@ -87,6 +123,10 @@ public class TypeConvertor { } public static UrlType castToUrl(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UrlType) return (UrlType) b; else if (b.hasPrimitiveValue()) @@ -96,6 +136,10 @@ public class TypeConvertor { } public static CanonicalType castToCanonical(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CanonicalType) return (CanonicalType) b; else if (b.hasPrimitiveValue()) @@ -105,6 +149,10 @@ public class TypeConvertor { } public static DateType castToDate(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DateType) return (DateType) b; else if (b.hasPrimitiveValue()) @@ -114,6 +162,10 @@ public class TypeConvertor { } public static DateTimeType castToDateTime(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DateTimeType) return (DateTimeType) b; else if (b.fhirType().equals("dateTime")) @@ -123,6 +175,9 @@ public class TypeConvertor { } public static TimeType castToTime(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof TimeType) return (TimeType) b; else @@ -130,6 +185,9 @@ public class TypeConvertor { } public static CodeType castToCode(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof CodeType) return (CodeType) b; else if (b.isPrimitive()) @@ -139,6 +197,9 @@ public class TypeConvertor { } public static OidType castToOid(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof OidType) return (OidType) b; else @@ -146,6 +207,9 @@ public class TypeConvertor { } public static IdType castToId(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof IdType) return (IdType) b; else @@ -153,6 +217,9 @@ public class TypeConvertor { } public static UnsignedIntType castToUnsignedInt(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UnsignedIntType) return (UnsignedIntType) b; else @@ -160,6 +227,9 @@ public class TypeConvertor { } public static PositiveIntType castToPositiveInt(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof PositiveIntType) return (PositiveIntType) b; else @@ -167,6 +237,9 @@ public class TypeConvertor { } public static MarkdownType castToMarkdown(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof MarkdownType) return (MarkdownType) b; else if (b.hasPrimitiveValue()) @@ -176,6 +249,9 @@ public class TypeConvertor { } public static Annotation castToAnnotation(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Annotation) return (Annotation) b; else @@ -183,13 +259,21 @@ public class TypeConvertor { } public static Dosage castToDosage(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Dosage) return (Dosage) b; - else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); + else + throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); } public static Attachment castToAttachment(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Attachment) return (Attachment) b; else @@ -197,6 +281,10 @@ public class TypeConvertor { } public static Identifier castToIdentifier(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Identifier) return (Identifier) b; else @@ -204,6 +292,10 @@ public class TypeConvertor { } public static CodeableConcept castToCodeableConcept(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CodeableConcept) return (CodeableConcept) b; else if (b instanceof Element) { @@ -221,6 +313,10 @@ public class TypeConvertor { } public static CodeableReference castToCodeableReference(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CodeableReference) { return (CodeableReference) b; } else if (b instanceof CodeType) { @@ -240,6 +336,10 @@ public class TypeConvertor { } public static Population castToPopulation(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Population) return (Population) b; else @@ -248,6 +348,10 @@ public class TypeConvertor { public static Coding castToCoding(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Coding) return (Coding) b; else if (b instanceof Element) { @@ -270,6 +374,10 @@ public class TypeConvertor { } public static Quantity castToQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Quantity) return (Quantity) b; else @@ -277,6 +385,10 @@ public class TypeConvertor { } public static Money castToMoney(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Money) return (Money) b; else @@ -284,6 +396,10 @@ public class TypeConvertor { } public static Duration castToDuration(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Duration) return (Duration) b; else @@ -291,6 +407,11 @@ public class TypeConvertor { } public static SimpleQuantity castToSimpleQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } + + if (b instanceof SimpleQuantity) return (SimpleQuantity) b; else if (b instanceof Quantity) { @@ -307,6 +428,10 @@ public class TypeConvertor { } public static Range castToRange(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Range) return (Range) b; else @@ -314,6 +439,10 @@ public class TypeConvertor { } public static Period castToPeriod(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Period) return (Period) b; else @@ -321,6 +450,10 @@ public class TypeConvertor { } public static Ratio castToRatio(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Ratio) return (Ratio) b; else @@ -328,6 +461,11 @@ public class TypeConvertor { } public static SampledData castToSampledData(Base b) throws FHIRException { + if (b == null) { + return null; + } + + if (b instanceof SampledData) return (SampledData) b; else @@ -335,6 +473,9 @@ public class TypeConvertor { } public static Signature castToSignature(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Signature) return (Signature) b; else @@ -342,6 +483,9 @@ public class TypeConvertor { } public static HumanName castToHumanName(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof HumanName) return (HumanName) b; else @@ -349,6 +493,9 @@ public class TypeConvertor { } public static Address castToAddress(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Address) return (Address) b; else @@ -356,6 +503,9 @@ public class TypeConvertor { } public static ContactDetail castToContactDetail(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ContactDetail) return (ContactDetail) b; else @@ -363,6 +513,9 @@ public class TypeConvertor { } public static Contributor castToContributor(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Contributor) return (Contributor) b; else @@ -370,6 +523,9 @@ public class TypeConvertor { } public static UsageContext castToUsageContext(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof UsageContext) return (UsageContext) b; else @@ -377,6 +533,9 @@ public class TypeConvertor { } public static RelatedArtifact castToRelatedArtifact(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof RelatedArtifact) return (RelatedArtifact) b; else @@ -384,6 +543,9 @@ public class TypeConvertor { } public static ContactPoint castToContactPoint(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ContactPoint) return (ContactPoint) b; else @@ -391,6 +553,9 @@ public class TypeConvertor { } public static Timing castToTiming(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Timing) return (Timing) b; else @@ -398,6 +563,9 @@ public class TypeConvertor { } public static Reference castToReference(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Reference) return (Reference) b; else if (b.isPrimitive() && Utilities.isURL(b.primitiveValue())) @@ -410,6 +578,9 @@ public class TypeConvertor { } public static Meta castToMeta(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Meta) return (Meta) b; else @@ -418,6 +589,9 @@ public class TypeConvertor { public static MarketingStatus castToMarketingStatus(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof MarketingStatus) return (MarketingStatus) b; else @@ -425,6 +599,9 @@ public class TypeConvertor { } public static Statistic castToStatistic(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Statistic) return (Statistic) b; else @@ -433,6 +610,9 @@ public class TypeConvertor { public static OrderedDistribution castToOrderedDistribution(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof OrderedDistribution) return (OrderedDistribution) b; else @@ -440,6 +620,9 @@ public class TypeConvertor { } public static ProductShelfLife castToProductShelfLife(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ProductShelfLife) return (ProductShelfLife) b; else @@ -447,6 +630,9 @@ public class TypeConvertor { } public static ProdCharacteristic castToProdCharacteristic(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ProdCharacteristic) return (ProdCharacteristic) b; else @@ -455,6 +641,9 @@ public class TypeConvertor { public static SubstanceAmount castToSubstanceAmount(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof SubstanceAmount) return (SubstanceAmount) b; else @@ -462,6 +651,9 @@ public class TypeConvertor { } public static Extension castToExtension(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Extension) return (Extension) b; else @@ -469,6 +661,9 @@ public class TypeConvertor { } public static Resource castToResource(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Resource) return (Resource) b; else @@ -476,6 +671,9 @@ public class TypeConvertor { } public static Narrative castToNarrative(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Narrative) return (Narrative) b; else @@ -484,6 +682,9 @@ public class TypeConvertor { public static ElementDefinition castToElementDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ElementDefinition) return (ElementDefinition) b; else @@ -491,6 +692,9 @@ public class TypeConvertor { } public static DataRequirement castToDataRequirement(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DataRequirement) return (DataRequirement) b; else @@ -498,6 +702,9 @@ public class TypeConvertor { } public static Expression castToExpression(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Expression) return (Expression) b; else @@ -506,6 +713,9 @@ public class TypeConvertor { public static ParameterDefinition castToParameterDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof ParameterDefinition) return (ParameterDefinition) b; else @@ -513,6 +723,9 @@ public class TypeConvertor { } public static TriggerDefinition castToTriggerDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof TriggerDefinition) return (TriggerDefinition) b; else @@ -520,6 +733,9 @@ public class TypeConvertor { } public static XhtmlNode castToXhtml(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Element) { return ((Element) b).getXhtml(); } else if (b instanceof XhtmlType) { @@ -535,6 +751,9 @@ public class TypeConvertor { } public static String castToXhtmlString(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Element) { return ((Element) b).getValue(); } else if (b instanceof XhtmlType) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/TypeConvertor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/TypeConvertor.java index f775d8979..10d483951 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/TypeConvertor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/TypeConvertor.java @@ -15,6 +15,9 @@ public class TypeConvertor { // -- converters for property setters public static DataType castToType(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DataType) return (DataType) b; else if (b.isMetadataBased()) @@ -25,6 +28,9 @@ public class TypeConvertor { public static BooleanType castToBoolean(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof BooleanType) return (BooleanType) b; else @@ -32,6 +38,9 @@ public class TypeConvertor { } public static IntegerType castToInteger(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof IntegerType) return (IntegerType) b; else @@ -39,6 +48,9 @@ public class TypeConvertor { } public static Integer64Type castToInteger64(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Integer64Type) return (Integer64Type) b; else @@ -46,6 +58,9 @@ public class TypeConvertor { } public static DecimalType castToDecimal(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof DecimalType) return (DecimalType) b; else if (b.hasPrimitiveValue()) @@ -55,6 +70,9 @@ public class TypeConvertor { } public static Base64BinaryType castToBase64Binary(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof Base64BinaryType) return (Base64BinaryType) b; else @@ -62,6 +80,9 @@ public class TypeConvertor { } public static InstantType castToInstant(Base b) throws FHIRException { + if (b == null) { + return null; + } if (b instanceof InstantType) return (InstantType) b; else @@ -69,6 +90,10 @@ public class TypeConvertor { } public static StringType castToString(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof StringType) return (StringType) b; else if (b.hasPrimitiveValue()) @@ -78,6 +103,10 @@ public class TypeConvertor { } public static UriType castToUri(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UriType) return (UriType) b; else if (b.hasPrimitiveValue()) @@ -87,6 +116,10 @@ public class TypeConvertor { } public static UrlType castToUrl(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UrlType) return (UrlType) b; else if (b.hasPrimitiveValue()) @@ -96,6 +129,10 @@ public class TypeConvertor { } public static CanonicalType castToCanonical(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CanonicalType) return (CanonicalType) b; else if (b.hasPrimitiveValue()) @@ -105,6 +142,10 @@ public class TypeConvertor { } public static DateType castToDate(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DateType) return (DateType) b; else if (b.hasPrimitiveValue()) @@ -114,6 +155,10 @@ public class TypeConvertor { } public static DateTimeType castToDateTime(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DateTimeType) return (DateTimeType) b; else if (b.fhirType().equals("dateTime")) @@ -123,6 +168,10 @@ public class TypeConvertor { } public static TimeType castToTime(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof TimeType) return (TimeType) b; else @@ -130,6 +179,10 @@ public class TypeConvertor { } public static CodeType castToCode(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CodeType) return (CodeType) b; else if (b.isPrimitive()) @@ -139,6 +192,10 @@ public class TypeConvertor { } public static OidType castToOid(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof OidType) return (OidType) b; else @@ -146,6 +203,10 @@ public class TypeConvertor { } public static IdType castToId(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof IdType) return (IdType) b; else @@ -153,6 +214,10 @@ public class TypeConvertor { } public static UnsignedIntType castToUnsignedInt(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UnsignedIntType) return (UnsignedIntType) b; else @@ -160,6 +225,10 @@ public class TypeConvertor { } public static PositiveIntType castToPositiveInt(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof PositiveIntType) return (PositiveIntType) b; else @@ -167,6 +236,10 @@ public class TypeConvertor { } public static MarkdownType castToMarkdown(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof MarkdownType) return (MarkdownType) b; else if (b.hasPrimitiveValue()) @@ -176,6 +249,10 @@ public class TypeConvertor { } public static Annotation castToAnnotation(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Annotation) return (Annotation) b; else @@ -183,13 +260,22 @@ public class TypeConvertor { } public static Dosage castToDosage(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Dosage) return (Dosage) b; - else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); + else + throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction"); } public static Attachment castToAttachment(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Attachment) return (Attachment) b; else @@ -197,6 +283,10 @@ public class TypeConvertor { } public static Identifier castToIdentifier(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Identifier) return (Identifier) b; else @@ -204,6 +294,10 @@ public class TypeConvertor { } public static CodeableConcept castToCodeableConcept(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CodeableConcept) return (CodeableConcept) b; else if (b instanceof Element) { @@ -221,6 +315,10 @@ public class TypeConvertor { } public static CodeableReference castToCodeableReference(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof CodeableReference) { return (CodeableReference) b; } else if (b instanceof CodeType) { @@ -240,6 +338,10 @@ public class TypeConvertor { } public static Population castToPopulation(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Population) return (Population) b; else @@ -248,6 +350,10 @@ public class TypeConvertor { public static Coding castToCoding(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Coding) return (Coding) b; else if (b instanceof Element) { @@ -270,6 +376,10 @@ public class TypeConvertor { } public static Quantity castToQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Quantity) return (Quantity) b; else @@ -277,6 +387,10 @@ public class TypeConvertor { } public static Count castToCount(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Count) return (Count) b; else @@ -284,6 +398,10 @@ public class TypeConvertor { } public static Money castToMoney(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Money) return (Money) b; else @@ -291,6 +409,10 @@ public class TypeConvertor { } public static Duration castToDuration(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Duration) return (Duration) b; else @@ -298,6 +420,10 @@ public class TypeConvertor { } public static SimpleQuantity castToSimpleQuantity(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof SimpleQuantity) return (SimpleQuantity) b; else if (b instanceof Quantity) { @@ -314,6 +440,10 @@ public class TypeConvertor { } public static Range castToRange(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Range) return (Range) b; else @@ -321,6 +451,10 @@ public class TypeConvertor { } public static Period castToPeriod(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Period) return (Period) b; else @@ -328,6 +462,10 @@ public class TypeConvertor { } public static Ratio castToRatio(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Ratio) return (Ratio) b; else @@ -335,6 +473,10 @@ public class TypeConvertor { } public static SampledData castToSampledData(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof SampledData) return (SampledData) b; else @@ -342,6 +484,10 @@ public class TypeConvertor { } public static Signature castToSignature(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Signature) return (Signature) b; else @@ -349,6 +495,10 @@ public class TypeConvertor { } public static HumanName castToHumanName(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof HumanName) return (HumanName) b; else @@ -356,6 +506,10 @@ public class TypeConvertor { } public static Address castToAddress(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Address) return (Address) b; else @@ -363,6 +517,10 @@ public class TypeConvertor { } public static ContactDetail castToContactDetail(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ContactDetail) return (ContactDetail) b; else @@ -370,6 +528,10 @@ public class TypeConvertor { } public static Contributor castToContributor(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Contributor) return (Contributor) b; else @@ -377,6 +539,10 @@ public class TypeConvertor { } public static UsageContext castToUsageContext(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof UsageContext) return (UsageContext) b; else @@ -384,6 +550,10 @@ public class TypeConvertor { } public static RelatedArtifact castToRelatedArtifact(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof RelatedArtifact) return (RelatedArtifact) b; else @@ -391,6 +561,10 @@ public class TypeConvertor { } public static ContactPoint castToContactPoint(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ContactPoint) return (ContactPoint) b; else @@ -398,6 +572,10 @@ public class TypeConvertor { } public static Timing castToTiming(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Timing) return (Timing) b; else @@ -405,6 +583,10 @@ public class TypeConvertor { } public static Reference castToReference(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Reference) return (Reference) b; else if (b.isPrimitive() && Utilities.isURL(b.primitiveValue())) @@ -417,6 +599,10 @@ public class TypeConvertor { } public static Meta castToMeta(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Meta) return (Meta) b; else @@ -425,6 +611,10 @@ public class TypeConvertor { public static MarketingStatus castToMarketingStatus(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof MarketingStatus) return (MarketingStatus) b; else @@ -432,6 +622,10 @@ public class TypeConvertor { } public static Statistic castToStatistic(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Statistic) return (Statistic) b; else @@ -440,13 +634,21 @@ public class TypeConvertor { public static OrderedDistribution castToOrderedDistribution(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof OrderedDistribution) return (OrderedDistribution) b; else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a OrderedDistribution"); } - public static ProductShelfLife castToProductShelfLife(Base b) throws FHIRException { + public static ProductShelfLife castToProductShelfLife(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ProductShelfLife) return (ProductShelfLife) b; else @@ -454,6 +656,10 @@ public class TypeConvertor { } public static ProdCharacteristic castToProdCharacteristic(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ProdCharacteristic) return (ProdCharacteristic) b; else @@ -462,6 +668,10 @@ public class TypeConvertor { public static SubstanceAmount castToSubstanceAmount(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof SubstanceAmount) return (SubstanceAmount) b; else @@ -469,6 +679,10 @@ public class TypeConvertor { } public static Extension castToExtension(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Extension) return (Extension) b; else @@ -476,6 +690,10 @@ public class TypeConvertor { } public static Resource castToResource(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Resource) return (Resource) b; else @@ -483,6 +701,10 @@ public class TypeConvertor { } public static Narrative castToNarrative(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Narrative) return (Narrative) b; else @@ -491,6 +713,10 @@ public class TypeConvertor { public static ElementDefinition castToElementDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ElementDefinition) return (ElementDefinition) b; else @@ -498,6 +724,10 @@ public class TypeConvertor { } public static DataRequirement castToDataRequirement(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof DataRequirement) return (DataRequirement) b; else @@ -505,6 +735,10 @@ public class TypeConvertor { } public static Expression castToExpression(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Expression) return (Expression) b; else @@ -513,6 +747,10 @@ public class TypeConvertor { public static ParameterDefinition castToParameterDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ParameterDefinition) return (ParameterDefinition) b; else @@ -520,6 +758,10 @@ public class TypeConvertor { } public static TriggerDefinition castToTriggerDefinition(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof TriggerDefinition) return (TriggerDefinition) b; else @@ -527,6 +769,10 @@ public class TypeConvertor { } public static ExtendedContactDetail castToExtendedContactDetail(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof ExtendedContactDetail) return (ExtendedContactDetail) b; else @@ -537,6 +783,10 @@ public class TypeConvertor { public static XhtmlNode castToXhtml(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Element) { return ((Element) b).getXhtml(); } else if (b instanceof XhtmlType) { @@ -552,6 +802,10 @@ public class TypeConvertor { } public static String castToXhtmlString(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Element) { return ((Element) b).getValue(); } else if (b instanceof XhtmlType) { @@ -568,6 +822,10 @@ public class TypeConvertor { public static VirtualServiceDetail castToVirtualServiceDetail(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof VirtualServiceDetail) return (VirtualServiceDetail) b; else @@ -576,6 +834,10 @@ public class TypeConvertor { public static Availability castToAvailability(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof Availability) return (Availability) b; else @@ -584,6 +846,10 @@ public class TypeConvertor { public static MonetaryComponent castToMonetaryComponent(Base b) throws FHIRException { + if (b == null) { + return null; + } + if (b instanceof MonetaryComponent) return (MonetaryComponent) b; else From 1df68c95a44f6f8f445c93a7e4cbfea08be15054 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 16 Jan 2023 10:13:11 +1100 Subject: [PATCH 8/9] Fix rendering when binding strength missing --- .../hl7/fhir/r5/renderers/StructureDefinitionRenderer.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 619d5e960..32125968a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -382,7 +382,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { public void scanBindings(Set cols, List list, ElementDefinition ed) { if (ed.hasBinding()) { - if (ed.getBinding().hasValueSet()) { + if (ed.getBinding().hasValueSet() && ed.getBinding().hasStrength()) { switch (ed.getBinding().getStrength()) { case EXAMPLE: cols.add("example"); @@ -770,7 +770,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { List res = new ArrayList<>(); if (element.hasBinding()) { ElementDefinitionBindingComponent b = element.getBinding(); - if (type.equals(b.getStrength().toCode())) { + if (b.hasStrength() && type.equals(b.getStrength().toCode())) { ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); res.add(ab.setAny(false).setDocumentation(b.getDescription()).setValueSet(b.getValueSet())); } @@ -990,6 +990,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) { + if (context.getStructureMode() != StructureDefinitionRendererMode.SUMMARY) { + return prow; + } Row row = gen.new Row(); row.setAnchor(parent.getPath()+"-"+grp.getName()); row.setColor(context.getProfileUtilities().getRowColor(parent, isConstraintMode)); From cf4e2f2eaf18817e0e1d40b212e39c85d3a27b96 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 16 Jan 2023 10:17:30 +1100 Subject: [PATCH 9/9] Fix problem converting ActivityDefinition R4/R5 --- .../ActivityDefinition40_50.java | 20 +++++++++++-------- .../ActivityDefinition43_50.java | 20 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java index 7d046c543..0ecc71ced 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java @@ -1,5 +1,6 @@ package org.hl7.fhir.convertors.conv40_50.resources40_50; +import org.hl7.fhir.convertors.VersionConvertorConstants; import org.hl7.fhir.convertors.context.ConversionContext40_50; import org.hl7.fhir.convertors.conv40_50.datatypes40_50.general40_50.CodeableConcept40_50; import org.hl7.fhir.convertors.conv40_50.datatypes40_50.general40_50.Identifier40_50; @@ -19,6 +20,7 @@ import org.hl7.fhir.convertors.conv40_50.datatypes40_50.primitive40_50.Uri40_50; import org.hl7.fhir.convertors.conv40_50.datatypes40_50.special40_50.Dosage40_50; import org.hl7.fhir.convertors.conv40_50.datatypes40_50.special40_50.Reference40_50; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeableReference; /* @@ -295,9 +297,10 @@ public class ActivityDefinition40_50 { case SUPPLYREQUEST: tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.SUPPLYREQUEST); break; -// case TASK: -// tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.TASK); -// break; + case TASK: + tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.NULL); + tgt.addExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME, new CodeType("Task")); + break; case VISIONPRESCRIPTION: tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.VISIONPRESCRIPTION); break; @@ -312,8 +315,11 @@ public class ActivityDefinition40_50 { if (src == null || src.isEmpty()) return null; org.hl7.fhir.r4.model.Enumeration tgt = new org.hl7.fhir.r4.model.Enumeration<>(new org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKindEnumFactory()); - ConversionContext40_50.INSTANCE.getVersionConvertor_40_50().copyElement(src, tgt); - switch (src.getValue()) { + ConversionContext40_50.INSTANCE.getVersionConvertor_40_50().copyElement(src, tgt, VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME); + if (src.hasExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME)) { + tgt.setValueAsString(src.getExtensionString(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME)); + } else { + switch (src.getValue()) { case APPOINTMENT: tgt.setValue(org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKind.APPOINTMENT); break; @@ -353,15 +359,13 @@ public class ActivityDefinition40_50 { case SUPPLYREQUEST: tgt.setValue(org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKind.SUPPLYREQUEST); break; -// case TASK: -// tgt.setValue(org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKind.TASK); -// break; case VISIONPRESCRIPTION: tgt.setValue(org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKind.VISIONPRESCRIPTION); break; default: tgt.setValue(org.hl7.fhir.r4.model.ActivityDefinition.ActivityDefinitionKind.NULL); break; + } } return tgt; } diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java index bcf5e6d98..3a9b39bba 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java @@ -1,5 +1,6 @@ package org.hl7.fhir.convertors.conv43_50.resources43_50; +import org.hl7.fhir.convertors.VersionConvertorConstants; import org.hl7.fhir.convertors.context.ConversionContext43_50; import org.hl7.fhir.convertors.conv43_50.datatypes43_50.general43_50.CodeableConcept43_50; import org.hl7.fhir.convertors.conv43_50.datatypes43_50.general43_50.Identifier43_50; @@ -19,6 +20,7 @@ import org.hl7.fhir.convertors.conv43_50.datatypes43_50.primitive43_50.Uri43_50; import org.hl7.fhir.convertors.conv43_50.datatypes43_50.special43_50.Dosage43_50; import org.hl7.fhir.convertors.conv43_50.datatypes43_50.special43_50.Reference43_50; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeableReference; /* @@ -295,9 +297,10 @@ public class ActivityDefinition43_50 { case SUPPLYREQUEST: tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.SUPPLYREQUEST); break; -// case TASK: -// tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.); -// break; + case TASK: + tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.NULL); + tgt.addExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME, new CodeType("Task")); + break; case VISIONPRESCRIPTION: tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.VISIONPRESCRIPTION); break; @@ -312,8 +315,11 @@ public class ActivityDefinition43_50 { if (src == null || src.isEmpty()) return null; org.hl7.fhir.r4b.model.Enumeration tgt = new org.hl7.fhir.r4b.model.Enumeration<>(new org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceTypeEnumFactory()); - ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().copyElement(src, tgt); - switch (src.getValue()) { + ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().copyElement(src, tgt, VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME); + if (src.hasExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME)) { + tgt.setValueAsString(src.getExtensionString(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME)); + } else { + switch (src.getValue()) { case APPOINTMENT: tgt.setValue(org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceType.APPOINTMENT); break; @@ -353,15 +359,13 @@ public class ActivityDefinition43_50 { case SUPPLYREQUEST: tgt.setValue(org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceType.SUPPLYREQUEST); break; -// case TASK: -// tgt.setValue(org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceType.TASK); -// break; case VISIONPRESCRIPTION: tgt.setValue(org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceType.VISIONPRESCRIPTION); break; default: tgt.setValue(org.hl7.fhir.r4b.model.ActivityDefinition.RequestResourceType.NULL); break; + } } return tgt; }