upgrade to pass new reconciled tests

This commit is contained in:
Grahame Grieve 2020-08-27 12:36:10 +10:00
parent 765f24a233
commit e669103aa7
13 changed files with 896 additions and 109 deletions

View File

@ -37,6 +37,7 @@ import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.hl7.fhir.utilities.DateTimeUtil;
import org.hl7.fhir.utilities.Utilities;
import java.util.Calendar;
import java.util.Date;
@ -283,6 +284,13 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
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
* <p>
@ -855,43 +863,80 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
* Caveat: this implementation assumes local timezone for unspecified timezones
*/
public Boolean equalsUsingFhirPathRules(BaseDateTimeType theOther) {
TemporalPrecisionEnum mp = this.myPrecision == TemporalPrecisionEnum.MILLI ? TemporalPrecisionEnum.SECOND : this.myPrecision;
TemporalPrecisionEnum op = theOther.myPrecision == TemporalPrecisionEnum.MILLI ? TemporalPrecisionEnum.SECOND : theOther.myPrecision;
TemporalPrecisionEnum cp = (mp.compareTo(op) < 0) ? mp : op;
FastDateFormat df = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
String ms = df.format(this.getValue());
String os = df.format(theOther.getValue());
if (!sub(ms, cp.stringLength()).equals(sub(os, cp.stringLength()))) {
if (hasTimezone() != theOther.hasTimezone()) {
if (!couldBeTheSameTime(this, theOther)) {
return false;
} else if (getPrecision() != theOther.getPrecision()) {
return false;
} else {
return null;
}
} else {
BaseDateTimeType left = (BaseDateTimeType) this.copy();
BaseDateTimeType right = (BaseDateTimeType) theOther.copy();
if (left.hasTimezone() && left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
left.setTimeZoneZulu(true);
}
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) {
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);
}
System.out.print("["+((lowLeft / 1000) - 130000000)+"-"+((highLeft / 1000) - 130000000)+"] vs ["+((lowRight / 1000) - 130000000)+"-"+((highRight / 1000) - 130000000)+"] = ");
if (highRight < lowLeft) {
System.out.println("false");
return false;
}
if (mp != op) {
return null;
}
if (this.myPrecision == TemporalPrecisionEnum.MILLI || theOther.myPrecision == TemporalPrecisionEnum.MILLI) {
float mf = Float.parseFloat(ms.substring(17));
float of = Float.parseFloat(os.substring(17));
if (mf != of) {
return false;
}
if (highLeft < lowRight) {
System.out.println("false");
return false;
}
System.out.println("true");
return true;
}
private String sub(String ms, int i) {
return ms.length() < i ? ms : ms.substring(0, i);
}
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 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() {
@ -904,4 +949,65 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
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.getSecondsMilli() < right.getSecondsMilli()) {
return -1;
} else if (left.getSecondsMilli() > right.getSecondsMilli()) {
return 1;
} else {
return 0;
}
}
}

View File

@ -72,7 +72,9 @@ public class ExpressionNode {
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,
Children, Descendants, MemberOf, Trace, Check, Today, Now, Resolve, Extension, AllFalse, AnyFalse, AllTrue, AnyTrue,
HasValue, 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,
// Local extensions to FHIRPath
@ -159,8 +161,19 @@ 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;
return null;
}
public String toCode() {
@ -244,8 +257,20 @@ 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";
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";
default: return "?custom?";
}
}

View File

@ -34,6 +34,8 @@ package org.hl7.fhir.r5.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,7 +49,17 @@ import java.util.List;
*/
public class Property {
/**
public static class PropertyMatcher implements IMatcher<Property> {
@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
*/
private String name;

View File

@ -624,6 +624,13 @@ public class Quantity extends DataType implements ICompositeType, ICoding {
res.setCode(code);
return res;
}
@Override
public String toString() {
return getValue().toPlainString()+ (hasUnit() || hasCode() ? " '"+(hasUnit() ? getUnit() : getCode())+"'" : "");
}
// end addition
}

View File

@ -1,5 +1,9 @@
package org.hl7.fhir.r5.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,67 @@ public class TimeType extends PrimitiveType<String> {
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));
}
}
}

View File

@ -32,6 +32,7 @@ package org.hl7.fhir.r5.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<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal));
public static class ProfiledType {
private String uri;

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.r5.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.rmi.server.LoaderHandler;
import java.util.ArrayList;
import java.util.Base64;
@ -38,6 +39,7 @@ import org.hl7.fhir.r5.model.ExpressionNode.Function;
import org.hl7.fhir.r5.model.ExpressionNode.Kind;
import org.hl7.fhir.r5.model.ExpressionNode.Operation;
import org.hl7.fhir.r5.model.ExpressionNode.SourceLocation;
import org.hl7.fhir.r5.model.Property.PropertyMatcher;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Quantity;
@ -54,6 +56,8 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r5.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.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -539,30 +543,59 @@ 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
*
@ -1239,8 +1272,19 @@ 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 Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
}
return false;
@ -1378,7 +1422,9 @@ public class FHIRPathEngine {
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()));
@ -1426,23 +1472,65 @@ public class FHIRPathEngine {
}
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("+");
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 (i == -1) {
i = v.substring(10).indexOf("Z");
}
v = i == -1 ? value : v.substring(0, 10+i);
}
if (v.length() > 10) {
return new DateTimeType(value).noExtensions();
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(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(value).noExtensions();
return new DateType(date).noExtensions();
}
}
@ -1655,6 +1743,8 @@ public class FHIRPathEngine {
result.addType(TypeDetails.FP_Integer);
} else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
result.addType(TypeDetails.FP_Decimal);
} else if (left.hasType(worker, "Quantity") && right.hasType(worker, "Quantity")) {
result.addType(TypeDetails.FP_Quantity);
}
return result;
case Div:
@ -1758,7 +1848,7 @@ public class FHIRPathEngine {
return left.equals(right);
}
private Boolean compareDates(BaseDateTimeType left, BaseDateTimeType right) {
private Boolean datesEqual(BaseDateTimeType left, BaseDateTimeType right) {
return left.equalsUsingFhirPathRules(right);
}
@ -1766,7 +1856,7 @@ public class FHIRPathEngine {
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());
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()) {
@ -1776,7 +1866,6 @@ public class FHIRPathEngine {
}
}
private boolean doEquivalent(Base left, Base right) throws PathEngineException {
if (left instanceof Quantity && right instanceof Quantity) {
return qtyEquivalent((Quantity) left, (Quantity) right);
@ -1791,13 +1880,44 @@ public class FHIRPathEngine {
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;
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));
}
throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()));
if (left.isPrimitive() && right.isPrimitive()) {
return Utilities.equivalent(left.primitiveValue(), right.primitiveValue());
}
if (!left.isPrimitive() && !right.isPrimitive()) {
MergedList<Property> props = new MergedList<Property>(left.children(), right.children(), new PropertyMatcher());
for (MergeNode<Property> t : props) {
if (t.hasLeft() && t.hasRight()) {
if (t.getLeft().hasValues() && t.getRight().hasValues()) {
MergedList<Base> values = new MergedList<Base>(t.getLeft().getValues(), t.getRight().getValues());
for (MergeNode<Base> 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) {
@ -1816,7 +1936,7 @@ public class FHIRPathEngine {
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 new DecimalType(c.getValue().asDecimal());
} catch (UcumException e) {
@ -1912,9 +2032,19 @@ public class FHIRPathEngine {
} 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);
Integer i = compareDateTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i < 0);
}
} else if ((l.hasType("time")) && (r.hasType("time"))) {
return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
Integer i = compareTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i < 0);
}
} else {
throw makeException(I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
}
@ -1949,9 +2079,19 @@ public class FHIRPathEngine {
} 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);
Integer i = compareDateTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i > 0);
}
} else if ((l.hasType("time")) && (r.hasType("time"))) {
return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
Integer i = compareTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i > 0);
}
} else {
throw makeException(I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
}
@ -1987,9 +2127,19 @@ public class FHIRPathEngine {
} 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);
Integer i = compareDateTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i <= 0);
}
} else if ((l.hasType("time")) && (r.hasType("time"))) {
return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
Integer i = compareTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i <= 0);
}
} else {
throw makeException(I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
}
@ -2027,9 +2177,19 @@ public class FHIRPathEngine {
} 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);
Integer i = compareDateTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i >= 0);
}
} else if ((l.hasType("time")) && (r.hasType("time"))) {
return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
Integer i = compareTimeElements(l, r, false);
if (i == null) {
return makeNull();
} else {
return makeBoolean(i >= 0);
}
} else {
throw makeException(I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
}
@ -2336,13 +2496,13 @@ public class FHIRPathEngine {
if (left.size() > 1) {
throw makeException(I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "-");
}
if (!left.get(0).isPrimitive()) {
if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) {
throw makeException(I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType());
}
if (right.size() > 1) {
throw makeException(I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "-");
}
if (!right.get(0).isPrimitive()) {
if (!right.get(0).isPrimitive() && !right.get(0).hasType("Quantity")) {
throw makeException(I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType());
}
@ -2354,6 +2514,12 @@ public class FHIRPathEngine {
result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
} else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
} else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) {
String s = l.primitiveValue();
if ("0".equals(s)) {
Quantity qty = (Quantity) r;
result.add(qty.copy().setValue(qty.getValue().abs()));
}
} else {
throw makeException(I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType());
}
@ -2388,17 +2554,17 @@ 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(), e);
// just return nothing
}
} else {
throw makeException(I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType());
@ -2428,7 +2594,10 @@ public class FHIRPathEngine {
Base r = right.get(0);
if (l.hasType("integer") && r.hasType("integer")) {
result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue())));
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 {
@ -2436,7 +2605,7 @@ public class FHIRPathEngine {
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(I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType());
@ -2465,7 +2634,10 @@ public class FHIRPathEngine {
Base r = right.get(0);
if (l.hasType("integer") && r.hasType("integer")) {
result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue())));
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 {
@ -2876,11 +3048,11 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case ToDateTime : {
checkContextPrimitive(focus, "toBoolean", false);
checkContextPrimitive(focus, "ToDateTime", false);
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
}
case ToTime : {
checkContextPrimitive(focus, "toBoolean", false);
checkContextPrimitive(focus, "ToTime", false);
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
}
case ConvertsToString :
@ -2891,6 +3063,7 @@ public class FHIRPathEngine {
case ConvertsToInteger :
case ConvertsToDecimal :
case ConvertsToDateTime :
case ConvertsToDate :
case ConvertsToTime :
case ConvertsToBoolean : {
checkContextPrimitive(focus, exp.getFunction().toCode(), false);
@ -2900,6 +3073,42 @@ public class FHIRPathEngine {
checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case Abs : {
checkContextNumerical(focus, "abs");
return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());
}
case Truncate :
case Floor :
case Ceiling : {
checkContextDecimal(focus, exp.getFunction().toCode());
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
}
case Round :{
checkContextDecimal(focus, "round");
if (paramTypes.size() > 0) {
checkParamTypes(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());
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
}
case Log : {
checkContextNumerical(focus, exp.getFunction().toCode());
checkParamTypes(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());
checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());
}
case Custom : {
return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
}
@ -2962,7 +3171,18 @@ public class FHIRPathEngine {
throw makeException(I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString());
}
}
private void checkContextNumerical(TypeDetails focus, String name) throws PathEngineException {
if (!focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
throw makeException(I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe());
}
}
private void checkContextDecimal(TypeDetails focus, String name) throws PathEngineException {
if (!focus.hasType("decimal") && !focus.hasType("integer")) {
throw makeException(I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe());
}
}
private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException {
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
@ -3061,8 +3281,20 @@ 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 Custom: {
List<List<Base>> params = new ArrayList<List<Base>>();
for (ExpressionNode p : exp.getParameters()) {
@ -3075,7 +3307,222 @@ public class FHIRPathEngine {
}
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "sqrt", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "abs", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "ceiling", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "floor", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "exp", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "ln", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "log", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
if (n1.size() != 1) {
throw makeException(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(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<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "power", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
if (n1.size() != 1) {
throw makeException(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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "truncate", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
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(I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
}
return result;
}
private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
if (focus.size() != 1) {
throw makeException(I18nConstants.FHIRPATH_FOCUS_PLURAL, "round", focus.size());
}
Base base = focus.get(0);
List<Base> result = new ArrayList<Base>();
if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
int i = 0;
if (exp.getParameters().size() == 1) {
List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
if (n1.size() != 1) {
throw makeException(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(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++) {
@ -3633,7 +4080,7 @@ public class FHIRPathEngine {
private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
if (focus.size() == 0 || focus.size() > 1) {
return makeBoolean(false);
return makeNull();
}
String ns = null;
String n = null;
@ -3648,7 +4095,7 @@ public class FHIRPathEngine {
}
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 {
@ -3660,7 +4107,15 @@ public class FHIRPathEngine {
return makeBoolean(false);
}
if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) {
return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType())));
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);
}
@ -4287,7 +4742,22 @@ public class FHIRPathEngine {
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());
("([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<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> result = new ArrayList<Base>();
if (focus.size() != 1) {
result.add(new BooleanType(false).noExtensions());
} 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());
}
@ -4316,7 +4786,7 @@ public class FHIRPathEngine {
result.add(new BooleanType(true).noExtensions());
} else if (focus.get(0) instanceof StringType) {
result.add(new BooleanType((convertToString(focus.get(0)).matches
("T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))).noExtensions());
("(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());
}

View File

@ -55,12 +55,12 @@ public class BaseDateTimeTypeTest {
Assertions.assertFalse(compareDateTimes("2001-01-02T11:22:33.445Z", "2001-01-02T11:22:33.444Z"));
// FHIRPath tests:
Assertions.assertNull(compareDateTimes("1974-12-25", "1974-12-25T12:34:00+10:00"));
Assertions.assertNull(compareDateTimes("1974-12-25T12:34:00+10:00", "1974-12-25"));
Assertions.assertFalse(compareDateTimes("1974-12-25", "1974-12-25T12:34:00+10:00"));
Assertions.assertFalse(compareDateTimes("1974-12-25T12:34:00+10:00", "1974-12-25"));
Assertions.assertFalse(compareDateTimes("1974-12-25", "1974-12-23T12:34:00+10:00")); // false because they can't be the same date irrespective of precision
Assertions.assertFalse(compareDateTimes("1974-12-23T12:34:00+10:00", "1974-12-25"));
Assertions.assertNull(compareDateTimes("1974-12-25", "1974-12-25T12:34:00Z"));
Assertions.assertNull(compareDateTimes("1974-12-25T12:34:00Z", "1974-12-25"));
Assertions.assertFalse(compareDateTimes("1974-12-25", "1974-12-25T12:34:00Z"));
Assertions.assertFalse(compareDateTimes("1974-12-25T12:34:00Z", "1974-12-25"));
Assertions.assertFalse(compareDateTimes("2012-04-15", "2012-04-16"));
Assertions.assertFalse(compareDateTimes("2012-04-16", "2012-04-15"));
Assertions.assertFalse(compareDateTimes("2012-04-15T15:00:00", "2012-04-15T10:00:00"));
@ -70,8 +70,8 @@ public class BaseDateTimeTypeTest {
Assertions.assertNull(compareDateTimes("1974-12-25", "1974-12-25T12:34:00"));
Assertions.assertNull(compareDateTimes("1974-12-25T12:34:00", "1974-12-25"));
Assertions.assertNull(compareDateTimes("2012-04-15T10:00:00", "2012-04-15"));
Assertions.assertFalse(compareDateTimes("2012-04-15T15:00:00Z", "2012-04-15T10:00:00"));
Assertions.assertFalse(compareDateTimes("2012-04-15T10:00:00", "2012-04-15T15:00:00Z"));
Assertions.assertNull(compareDateTimes("2012-04-15T15:00:00Z", "2012-04-15T10:00:00"));
Assertions.assertNull(compareDateTimes("2012-04-15T10:00:00", "2012-04-15T15:00:00Z"));
Assertions.assertTrue(compareDateTimes("1974-12-25", "1974-12-25"));
Assertions.assertTrue(compareDateTimes("2012-04-15", "2012-04-15"));
Assertions.assertTrue(compareDateTimes("2012-04-15T15:00:00+02:00", "2012-04-15T16:00:00+03:00"));
@ -80,7 +80,7 @@ public class BaseDateTimeTypeTest {
Assertions.assertTrue(compareDateTimes("2017-11-05T00:30:00.0-05:00", "2017-11-05T01:30:00.0-04:00"));
Assertions.assertFalse(compareDateTimes("2016-12-02T13:00:00Z", "2016-11-02T10:00:00")); // no timezone, but cannot be the same time
Assertions.assertFalse(compareDateTimes("2016-12-02T13:00:00Z", "2016-12-02T10:00:00")); // no timezone, might be the same time
Assertions.assertNull(compareDateTimes("2016-12-02T13:00:00Z", "2016-12-02T10:00:00")); // no timezone, might be the same time
}
@Test
@ -99,7 +99,6 @@ public class BaseDateTimeTypeTest {
private Boolean compareDateTimes(String theLeft, String theRight) {
System.out.println("Compare " + theLeft + " to " + theRight);
DateTimeType leftDt = new DateTimeType(theLeft);
DateTimeType rightDt = new DateTimeType(theRight);
return leftDt.equalsUsingFhirPathRules(rightDt);

View File

@ -178,8 +178,10 @@ public class FHIRPathTests {
outcome.clear();
outcome.add(new BooleanType(ok));
}
if (fp.hasLog())
if (fp.hasLog()) {
System.out.println(name);
System.out.println(fp.takeLog());
}
List<Element> expected = new ArrayList<Element>();
XMLUtil.getNamedChildren(test, "output", expected);
@ -214,6 +216,7 @@ public class FHIRPathTests {
} 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)).asStringValue()))) {
System.out.println(name);
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));
}
Assertions.assertEquals(v, ((PrimitiveType) outcome.get(i)).asStringValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));

View File

@ -0,0 +1,80 @@
package org.hl7.fhir.utilities;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.utilities.MergedList.MergeNode;
/**
* A list of items that represent
* @author graha
*
* @param <T>
*/
public class MergedList<T> extends ArrayList<MergeNode<T>> {
private static final long serialVersionUID = 1L;
public static interface IMatcher<T1> {
public boolean match(T1 l, T1 r);
}
public static class MergeNode<T1> {
private T1 left;
private T1 right;
public MergeNode(T1 left, T1 right) {
super();
this.left = left;
this.right = right;
}
public T1 getLeft() {
return left;
}
public T1 getRight() {
return right;
}
public boolean hasLeft() {
return left != null;
}
public boolean hasRight() {
return right != null;
}
@Override
public String toString() {
return (hasLeft() ? left.toString() : "null") + " :: "+(hasRight() ? right.toString() : "null");
}
}
public MergedList(List<T> left, List<T> right, IMatcher<T> matcher) {
super();
List<T> m = new ArrayList<>();
for (T l : left) {
T t = null;
for (T r : right) {
if (matcher.match(l, r)) {
t = r;
m.add(r);
break;
}
}
this.add(new MergeNode<T>(l, t));
}
for (T r : right) {
if (!m.contains(r)) {
this.add(new MergeNode<T>(null, r));
}
}
}
public MergedList(List<T> left, List<T> right) {
super();
for (int i = 0; i < Integer.max(left.size(), right.size()); i++) {
T l = i < left.size() ? left.get(i) : null;
T r = i < right.size() ? right.get(i) : null;
this.add(new MergeNode<T>(l, r));
}
}
}

View File

@ -42,6 +42,8 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
@ -1103,12 +1105,18 @@ public class Utilities {
return true;
if (Utilities.noString(l) || Utilities.noString(r))
return false;
l = l.toLowerCase().trim();
r = r.toLowerCase().trim(); // not that this should make any difference
return l.startsWith(r) || r.startsWith(l);
if (!Utilities.isDecimal(l, true) || !Utilities.isDecimal(r, true))
return false;
BigDecimal dl = new BigDecimal(l);
BigDecimal dr = new BigDecimal(r);
if (dl.scale() < dr.scale()) {
dr = dr.setScale(dl.scale(), RoundingMode.HALF_UP);
} else if (dl.scale() > dr.scale()) {
dl = dl.setScale(dr.scale(), RoundingMode.HALF_UP);
}
return dl.equals(dr);
}
public static String getFileExtension(String fn) {
return fn.contains(".") ? fn.substring(fn.lastIndexOf(".") + 1) : "";
}

View File

@ -117,10 +117,12 @@ public class I18nConstants {
public static final String EXTENSION_EXT_VERSION_NOCHANGE = "Extension_EXT_Version_NoChange";
public static final String EXTENSION_PROF_TYPE = "Extension_PROF_Type";
public static final String FHIRPATH_ALIAS_COLLECTION = "FHIRPATH_ALIAS_COLLECTION";
public static final String FHIRPATH_BAD_DATE = "FHIRPATH_BAD_DATE";
public static final String FHIRPATH_CANNOT_USE = "FHIRPATH_CANNOT_USE";
public static final String FHIRPATH_CANT_COMPARE = "FHIRPATH_CANT_COMPARE";
public static final String FHIRPATH_CHECK_FAILED = "FHIRPATH_CHECK_FAILED";
public static final String FHIRPATH_CODED_ONLY = "FHIRPATH_CODED_ONLY";
public static final String FHIRPATH_DECIMAL_ONLY = "FHIRPATH_DECIMAL_ONLY";
public static final String FHIRPATH_DISCRIMINATOR_BAD_NAME = "FHIRPATH_DISCRIMINATOR_BAD_NAME";
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST";
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP";
@ -136,6 +138,7 @@ public class I18nConstants {
public static final String FHIRPATH_DISCRIMINATOR_THIS_CANNOT_FIND = "FHIRPATH_DISCRIMINATOR_THIS_CANNOT_FIND";
public static final String FHIRPATH_DISCRIMINATOR_TYPE_MULTIPLE = "FHIRPATH_DISCRIMINATOR_TYPE_MULTIPLE";
public static final String FHIRPATH_DISCRIMINATOR_TYPE_NONE = "FHIRPATH_DISCRIMINATOR_TYPE_NONE";
public static final String FHIRPATH_FOCUS_PLURAL = "FHIRPATH_FOCUS_PLURAL";
public static final String FHIRPATH_HO_HOST_SERVICES = "FHIRPATH_HO_HOST_SERVICES";
public static final String FHIRPATH_LEFT_VALUE_PLURAL = "FHIRPATH_LEFT_VALUE_PLURAL";
public static final String FHIRPATH_LEFT_VALUE_WRONG_TYPE = "FHIRPATH_LEFT_VALUE_WRONG_TYPE";
@ -143,6 +146,7 @@ public class I18nConstants {
public static final String FHIRPATH_NOT_IMPLEMENTED = "FHIRPATH_NOT_IMPLEMENTED";
public static final String FHIRPATH_NO_COLLECTION = "FHIRPATH_NO_COLLECTION";
public static final String FHIRPATH_NO_TYPE = "FHIRPATH_NO_TYPE";
public static final String FHIRPATH_NUMERICAL_ONLY = "FHIRPATH_NUMERICAL_ONLY";
public static final String FHIRPATH_OP_INCOMPATIBLE = "FHIRPATH_OP_INCOMPATIBLE";
public static final String FHIRPATH_ORDERED_ONLY = "FHIRPATH_ORDERED_ONLY";
public static final String FHIRPATH_PARAM_WRONG = "FHIRPATH_PARAM_WRONG";

View File

@ -600,3 +600,7 @@ BUNDLE_RULE_PROFILE_UNKNOWN = Bundle Rules profile {1} is unknown for {0}
UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ = Unable to determine whether the provided codes are in the value set {0} because the value set or code system is not known to the validator
TERMINOLOGY_TX_SYSTEM_WRONG_HTML = The code system reference {0} is wrong - the code system reference cannot be to an HTML page. This may be the correct reference: {1}
TERMINOLOGY_TX_SYSTEM_WRONG_BUILD = The code system reference {0} is wrong - the code system reference cannot be a reference to build.fhir.org. This may be the correct reference: {1}
FHIRPATH_BAD_DATE = Unable to parse Date {0}
FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on integer, decimal or Quantity but found {1}
FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1}
FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value