001package org.hl7.fhir.r4.utils; 002 003//import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 004 005import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 006import ca.uhn.fhir.util.ElementUtil; 007import org.apache.commons.lang3.NotImplementedException; 008import org.fhir.ucum.Decimal; 009import org.fhir.ucum.Pair; 010import org.fhir.ucum.UcumException; 011import org.hl7.fhir.exceptions.DefinitionException; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.exceptions.PathEngineException; 014import org.hl7.fhir.r4.conformance.ProfileUtilities; 015import org.hl7.fhir.r4.context.IWorkerContext; 016import org.hl7.fhir.r4.model.*; 017import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 018import org.hl7.fhir.r4.model.ExpressionNode.*; 019import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 020import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 021import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; 022import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; 023import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; 024import org.hl7.fhir.utilities.Utilities; 025 026import java.math.BigDecimal; 027import java.util.*; 028 029/** 030 * @author Grahame Grieve 031 */ 032public class FHIRPathEngine { 033 private IWorkerContext worker; 034 private IEvaluationContext hostServices; 035 private StringBuilder log = new StringBuilder(); 036 private Set<String> primitiveTypes = new HashSet<String>(); 037 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 038 /** 039 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 040 */ 041 public FHIRPathEngine(IWorkerContext worker) { 042 super(); 043 this.worker = worker; 044 for (StructureDefinition sd : worker.allStructures()) { 045 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) 046 allTypes.put(sd.getName(), sd); 047 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 048 primitiveTypes.add(sd.getName()); 049 } 050 } 051 } 052 053 private TypeDetails anything(CollectionStatus status) { 054 return new TypeDetails(status, allTypes.keySet()); 055 } 056 057 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 058 return new ExecutionContext(context.appInfo, context.resource, context.context, context.aliases, newThis); 059 } 060 061 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 062 return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 063 } 064 065 066 // --- 3 methods to override in children ------------------------------------------------------- 067 // if you don't override, it falls through to the using the base reference implementation 068 // HAPI overrides to these to support extending the base model 069 070 /** 071 * check that paths referred to in the ExpressionNode are valid 072 * <p> 073 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 074 * <p> 075 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 076 * 077 * @param context - the logical type against which this path is applied 078 * @throws DefinitionException 079 * @throws PathEngineException 080 * @if the path is not valid 081 */ 082 public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 083 // if context is a path that refers to a type, do that conversion now 084 TypeDetails types; 085 if (context == null) { 086 types = null; // this is a special case; the first path reference will have to resolve to something in the context 087 } else if (!context.contains(".")) { 088 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context); 089 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 090 } else { 091 String ctxt = context.substring(0, context.indexOf('.')); 092 if (Utilities.isAbsoluteUrl(resourceType)) { 093 ctxt = resourceType.substring(0, resourceType.lastIndexOf("/") + 1) + ctxt; 094 } 095 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); 096 if (sd == null) 097 throw new PathEngineException("Unknown context " + context); 098 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 099 if (ed == null) 100 throw new PathEngineException("Unknown context element " + context); 101 if (ed.fixedType != null) 102 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 103 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 104 types = new TypeDetails(CollectionStatus.SINGLETON, ctxt + "#" + context); 105 else { 106 types = new TypeDetails(CollectionStatus.SINGLETON); 107 for (TypeRefComponent t : ed.getDefinition().getType()) 108 types.addType(t.getCode()); 109 } 110 } 111 112 return executeType(new ExecutionTypeContext(appContext, resourceType, context, types), types, expr, true); 113 } 114 115 public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 116 // if context is a path that refers to a type, do that conversion now 117 TypeDetails types; 118 if (!context.contains(".")) { 119 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 120 } else { 121 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 122 if (ed == null) 123 throw new PathEngineException("Unknown context element " + context); 124 if (ed.fixedType != null) 125 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 126 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 127 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl() + "#" + context); 128 else { 129 types = new TypeDetails(CollectionStatus.SINGLETON); 130 for (TypeRefComponent t : ed.getDefinition().getType()) 131 types.addType(t.getCode()); 132 } 133 } 134 135 return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), context, types), types, expr, true); 136 } 137 138 public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 139 // if context is a path that refers to a type, do that conversion now 140 TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context 141 return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true); 142 } 143 144 // --- public API ------------------------------------------------------- 145 146 public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 147 return check(appContext, resourceType, context, parse(expr)); 148 } 149 150 private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException { 151 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) 152 throw new PathEngineException("The function '" + name + "'() can only be used on string, code, uri, Coding, CodeableConcept"); 153 } 154 155 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty) throws PathEngineException { 156 if (canQty) { 157 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) 158 throw new PathEngineException("The function '" + name + "'() can only be used on a Quantity or on " + primitiveTypes.toString()); 159 } else if (!focus.hasType(primitiveTypes)) 160 throw new PathEngineException("The function '" + name + "'() can only be used on " + primitiveTypes.toString()); 161 } 162 163 private void checkContextReference(TypeDetails focus, String name) throws PathEngineException { 164 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) 165 throw new PathEngineException("The function '" + name + "'() can only be used on string, uri, canonical, Reference"); 166 } 167 168 private void checkContextString(TypeDetails focus, String name) throws PathEngineException { 169 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "id")) 170 throw new PathEngineException("The function '" + name + "'() can only be used on string, uri, code, id, but found " + focus.describe()); 171 } 172 173 private void checkOrdered(TypeDetails focus, String name) throws PathEngineException { 174 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) 175 throw new PathEngineException("The function '" + name + "'() can only be used on ordered collections"); 176 } 177 178 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 179 if (exp.getParameters().size() != count) 180 throw lexer.error("The function \"" + exp.getName() + "\" requires " + Integer.toString(count) + " parameters", location.toString()); 181 return true; 182 } 183 184 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 185 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) 186 throw lexer.error("The function \"" + exp.getName() + "\" requires between " + Integer.toString(countMin) + " and " + Integer.toString(countMax) + " parameters", location.toString()); 187 return true; 188 } 189 190 private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 191 int i = 0; 192 for (TypeDetails pt : typeSet) { 193 if (i == paramTypes.size()) 194 return; 195 TypeDetails actual = paramTypes.get(i); 196 i++; 197 for (String a : actual.getTypes()) { 198 if (!pt.hasType(worker, a)) 199 throw new PathEngineException("The parameter type '" + a + "' is not legal for " + funcName + " parameter " + Integer.toString(i) + ". expecting " + pt.toString()); 200 } 201 } 202 } 203 204 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 205 switch (exp.getFunction()) { 206 case Empty: 207 return checkParamCount(lexer, location, exp, 0); 208 case Not: 209 return checkParamCount(lexer, location, exp, 0); 210 case Exists: 211 return checkParamCount(lexer, location, exp, 0); 212 case SubsetOf: 213 return checkParamCount(lexer, location, exp, 1); 214 case SupersetOf: 215 return checkParamCount(lexer, location, exp, 1); 216 case IsDistinct: 217 return checkParamCount(lexer, location, exp, 0); 218 case Distinct: 219 return checkParamCount(lexer, location, exp, 0); 220 case Count: 221 return checkParamCount(lexer, location, exp, 0); 222 case Where: 223 return checkParamCount(lexer, location, exp, 1); 224 case Select: 225 return checkParamCount(lexer, location, exp, 1); 226 case All: 227 return checkParamCount(lexer, location, exp, 0, 1); 228 case Repeat: 229 return checkParamCount(lexer, location, exp, 1); 230 case Aggregate: 231 return checkParamCount(lexer, location, exp, 1, 2); 232 case Item: 233 return checkParamCount(lexer, location, exp, 1); 234 case As: 235 return checkParamCount(lexer, location, exp, 1); 236 case OfType: 237 return checkParamCount(lexer, location, exp, 1); 238 case Type: 239 return checkParamCount(lexer, location, exp, 0); 240 case Is: 241 return checkParamCount(lexer, location, exp, 1); 242 case Single: 243 return checkParamCount(lexer, location, exp, 0); 244 case First: 245 return checkParamCount(lexer, location, exp, 0); 246 case Last: 247 return checkParamCount(lexer, location, exp, 0); 248 case Tail: 249 return checkParamCount(lexer, location, exp, 0); 250 case Skip: 251 return checkParamCount(lexer, location, exp, 1); 252 case Take: 253 return checkParamCount(lexer, location, exp, 1); 254 case Union: 255 return checkParamCount(lexer, location, exp, 1); 256 case Combine: 257 return checkParamCount(lexer, location, exp, 1); 258 case Intersect: 259 return checkParamCount(lexer, location, exp, 1); 260 case Exclude: 261 return checkParamCount(lexer, location, exp, 1); 262 case Iif: 263 return checkParamCount(lexer, location, exp, 2, 3); 264 case Lower: 265 return checkParamCount(lexer, location, exp, 0); 266 case Upper: 267 return checkParamCount(lexer, location, exp, 0); 268 case ToChars: 269 return checkParamCount(lexer, location, exp, 0); 270 case Substring: 271 return checkParamCount(lexer, location, exp, 1, 2); 272 case StartsWith: 273 return checkParamCount(lexer, location, exp, 1); 274 case EndsWith: 275 return checkParamCount(lexer, location, exp, 1); 276 case Matches: 277 return checkParamCount(lexer, location, exp, 1); 278 case ReplaceMatches: 279 return checkParamCount(lexer, location, exp, 2); 280 case Contains: 281 return checkParamCount(lexer, location, exp, 1); 282 case Replace: 283 return checkParamCount(lexer, location, exp, 2); 284 case Length: 285 return checkParamCount(lexer, location, exp, 0); 286 case Children: 287 return checkParamCount(lexer, location, exp, 0); 288 case Descendants: 289 return checkParamCount(lexer, location, exp, 0); 290 case MemberOf: 291 return checkParamCount(lexer, location, exp, 1); 292 case Trace: 293 return checkParamCount(lexer, location, exp, 1); 294 case Today: 295 return checkParamCount(lexer, location, exp, 0); 296 case Now: 297 return checkParamCount(lexer, location, exp, 0); 298 case Resolve: 299 return checkParamCount(lexer, location, exp, 0); 300 case Extension: 301 return checkParamCount(lexer, location, exp, 1); 302 case HasValue: 303 return checkParamCount(lexer, location, exp, 0); 304 case Alias: 305 return checkParamCount(lexer, location, exp, 1); 306 case AliasAs: 307 return checkParamCount(lexer, location, exp, 1); 308 case HtmlChecks: 309 return checkParamCount(lexer, location, exp, 0); 310 case ToInteger: 311 return checkParamCount(lexer, location, exp, 0); 312 case ToDecimal: 313 return checkParamCount(lexer, location, exp, 0); 314 case ToString: 315 return checkParamCount(lexer, location, exp, 0); 316 case ToQuantity: 317 return checkParamCount(lexer, location, exp, 0); 318 case ToBoolean: 319 return checkParamCount(lexer, location, exp, 0); 320 case ToDateTime: 321 return checkParamCount(lexer, location, exp, 0); 322 case ToTime: 323 return checkParamCount(lexer, location, exp, 0); 324 case IsInteger: 325 return checkParamCount(lexer, location, exp, 0); 326 case IsDecimal: 327 return checkParamCount(lexer, location, exp, 0); 328 case IsString: 329 return checkParamCount(lexer, location, exp, 0); 330 case IsQuantity: 331 return checkParamCount(lexer, location, exp, 0); 332 case IsBoolean: 333 return checkParamCount(lexer, location, exp, 0); 334 case IsDateTime: 335 return checkParamCount(lexer, location, exp, 0); 336 case IsTime: 337 return checkParamCount(lexer, location, exp, 0); 338 case Custom: 339 return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 340 } 341 return false; 342 } 343 344 private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException { 345 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 346 for (String f : focus.getTypes()) 347 getChildTypesByName(f, mask, result); 348 return result; 349 } 350 351 private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { 352 String dateLeftString = theL.primitiveValue(); 353 DateTimeType dateLeft = new DateTimeType(dateLeftString); 354 355 String dateRightString = theR.primitiveValue(); 356 DateTimeType dateRight = new DateTimeType(dateRightString); 357 358 if (theEquivalenceTest) { 359 TemporalPrecisionEnum lowestPrecision = dateLeft.getPrecision().ordinal() < dateRight.getPrecision().ordinal() ? dateLeft.getPrecision() : dateRight.getPrecision(); 360 dateLeft.setPrecision(lowestPrecision); 361 dateRight.setPrecision(lowestPrecision); 362 } 363 364 if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 365 dateLeft.setTimeZoneZulu(true); 366 } 367 if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 368 dateRight.setTimeZoneZulu(true); 369 } 370 371 dateLeftString = dateLeft.getValueAsString(); 372 dateRightString = dateRight.getValueAsString(); 373 374 return dateLeftString.compareTo(dateRightString); 375 } 376 377 /** 378 * worker routine for converting a set of objects to a boolean representation (for invariants) 379 * 380 * @param items - result from @evaluate 381 * @return 382 */ 383 public boolean convertToBoolean(List<Base> items) { 384 if (items == null) 385 return false; 386 else if (items.size() == 1 && items.get(0) instanceof BooleanType) 387 return ((BooleanType) items.get(0)).getValue(); 388 else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model 389 return Boolean.valueOf(items.get(0).primitiveValue()); 390 else 391 return items.size() > 0; 392 } 393 394 /** 395 * worker routine for converting a set of objects to a string representation 396 * 397 * @param items - result from @evaluate 398 * @return 399 */ 400 public String convertToString(List<Base> items) { 401 StringBuilder b = new StringBuilder(); 402 boolean first = true; 403 for (Base item : items) { 404 if (first) 405 first = false; 406 else 407 b.append(','); 408 409 b.append(convertToString(item)); 410 } 411 return b.toString(); 412 } 413 414 private String convertToString(Base item) { 415 if (item.isPrimitive()) 416 return item.primitiveValue(); 417 else if (item instanceof Quantity) { 418 Quantity q = (Quantity) item; 419 if (q.getSystem().equals("http://unitsofmeasure.org")) { 420 String u = "'" + q.getCode() + "'"; 421 boolean plural = !q.getValue().toPlainString().equals("1"); 422 if ("a".equals(q.getCode())) 423 u = plural ? "years" : "year"; 424 else if ("mo".equals(q.getCode())) 425 u = plural ? "months" : "month"; 426 else if ("wk".equals(q.getCode())) 427 u = plural ? "weeks" : "week"; 428 else if ("d".equals(q.getCode())) 429 u = plural ? "days" : "day"; 430 else if ("h".equals(q.getCode())) 431 u = plural ? "hours" : "hour"; 432 else if ("min".equals(q.getCode())) 433 u = plural ? "minutes" : "minute"; 434 else if ("s".equals(q.getCode())) 435 u = plural ? "seconds" : "seconds"; 436 else if ("ms".equals(q.getCode())) 437 u = plural ? "milliseconds" : "milliseconds"; 438 return q.getValue().toPlainString() + " " + u; 439 } else 440 return item.toString(); 441 } else 442 return item.toString(); 443 } 444 445 private boolean doContains(List<Base> list, Base item) { 446 for (Base test : list) 447 if (doEquals(test, item)) 448 return true; 449 return false; 450 } 451 452 private boolean doEquals(Base left, Base right) { 453 if (left instanceof Quantity && right instanceof Quantity) 454 return qtyEqual((Quantity) left, (Quantity) right); 455 else if (left.isPrimitive() && right.isPrimitive()) 456 return Base.equals(left.primitiveValue(), right.primitiveValue()); 457 else 458 return Base.compareDeep(left, right, false); 459 } 460 461 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 462 if (left instanceof Quantity && right instanceof Quantity) 463 return qtyEquivalent((Quantity) left, (Quantity) right); 464 if (left.hasType("integer") && right.hasType("integer")) 465 return doEquals(left, right); 466 if (left.hasType("boolean") && right.hasType("boolean")) 467 return doEquals(left, right); 468 if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) 469 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 470 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) 471 return compareDateTimeElements(left, right, true) == 0; 472 if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri")) 473 return Utilities.equivalent(convertToString(left), convertToString(right)); 474 475 throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType())); 476 } 477 478 /** 479 * evaluate a path and return the matching elements 480 * 481 * @param base - the object against which the path is being evaluated 482 * @param ExpressionNode - the parsed ExpressionNode statement to use 483 * @return 484 * @throws FHIRException 485 * @ 486 */ 487 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { 488 List<Base> list = new ArrayList<Base>(); 489 if (base != null) 490 list.add(base); 491 log = new StringBuilder(); 492 return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); 493 } 494 495 /** 496 * evaluate a path and return the matching elements 497 * 498 * @param base - the object against which the path is being evaluated 499 * @param path - the FHIR Path statement to use 500 * @return 501 * @throws FHIRException 502 * @ 503 */ 504 public List<Base> evaluate(Base base, String path) throws FHIRException { 505 ExpressionNode exp = parse(path); 506 List<Base> list = new ArrayList<Base>(); 507 if (base != null) 508 list.add(base); 509 log = new StringBuilder(); 510 return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true); 511 } 512 513 /** 514 * evaluate a path and return the matching elements 515 * 516 * @param base - the object against which the path is being evaluated 517 * @param ExpressionNode - the parsed ExpressionNode statement to use 518 * @return 519 * @throws FHIRException 520 * @ 521 */ 522 public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 523 List<Base> list = new ArrayList<Base>(); 524 if (base != null) 525 list.add(base); 526 log = new StringBuilder(); 527 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 528 } 529 530 /** 531 * evaluate a path and return the matching elements 532 * 533 * @param base - the object against which the path is being evaluated 534 * @param ExpressionNode - the parsed ExpressionNode statement to use 535 * @return 536 * @throws FHIRException 537 * @ 538 */ 539 public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 540 List<Base> list = new ArrayList<Base>(); 541 if (base != null) 542 list.add(base); 543 log = new StringBuilder(); 544 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 545 } 546 547 /** 548 * evaluate a path and return the matching elements 549 * 550 * @param base - the object against which the path is being evaluated 551 * @param path - the FHIR Path statement to use 552 * @return 553 * @throws FHIRException 554 * @ 555 */ 556 public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException { 557 ExpressionNode exp = parse(path); 558 List<Base> list = new ArrayList<Base>(); 559 if (base != null) 560 list.add(base); 561 log = new StringBuilder(); 562 return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true); 563 } 564 565 /** 566 * given an element definition in a profile, what element contains the differentiating fixed 567 * for the element, given the differentiating expresssion. The expression is only allowed to 568 * use a subset of FHIRPath 569 * 570 * @param profile 571 * @param element 572 * @return 573 * @throws PathEngineException 574 * @throws DefinitionException 575 */ 576 public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException { 577 StructureDefinition sd = profile; 578 ElementDefinition focus = null; 579 580 if (expr.getKind() == Kind.Name) { 581 List<ElementDefinition> childDefinitions; 582 childDefinitions = ProfileUtilities.getChildMap(sd, element); 583 // if that's empty, get the children of the type 584 if (childDefinitions.isEmpty()) { 585 sd = fetchStructureByType(element); 586 if (sd == null) 587 throw new DefinitionException("Problem with use of resolve() - profile '" + element.getType().get(0).getProfile() + "' on " + element.getId() + " could not be resolved"); 588 childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 589 } 590 for (ElementDefinition t : childDefinitions) { 591 if (tailMatches(t, expr.getName())) { 592 focus = t; 593 break; 594 } 595 } 596 } else if (expr.getKind() == Kind.Function) { 597 if ("resolve".equals(expr.getName())) { 598 if (!element.hasType()) 599 throw new DefinitionException("illegal use of resolve() in discriminator - no type on element " + element.getId()); 600 if (element.getType().size() > 1) 601 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on " + element.getId()); 602 if (!element.getType().get(0).hasTarget()) 603 throw new DefinitionException("illegal use of resolve() in discriminator - type on " + element.getId() + " is not Reference (" + element.getType().get(0).getCode() + ")"); 604 if (element.getType().get(0).getTargetProfile().size() > 1) 605 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on " + element.getId()); 606 sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue()); 607 if (sd == null) 608 throw new DefinitionException("Problem with use of resolve() - profile '" + element.getType().get(0).getTargetProfile() + "' on " + element.getId() + " could not be resolved"); 609 focus = sd.getSnapshot().getElementFirstRep(); 610 } else if ("extension".equals(expr.getName())) { 611 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 612// targetUrl = targetUrl.substring(1,targetUrl.length()-1); 613 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(sd, element); 614 for (ElementDefinition t : childDefinitions) { 615 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 616 sd = worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue()); 617 while (sd != null && !sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) 618 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 619 if (sd.getUrl().equals(targetUrl)) { 620 focus = t; 621 break; 622 } 623 } 624 } 625 } else 626 throw new DefinitionException("illegal function name " + expr.getName() + "() in discriminator"); 627 } else if (expr.getKind() == Kind.Group) { 628 throw new DefinitionException("illegal expression syntax in discriminator (group)"); 629 } else if (expr.getKind() == Kind.Constant) { 630 throw new DefinitionException("illegal expression syntax in discriminator (const)"); 631 } 632 633 if (focus == null) 634 throw new DefinitionException("Unable to resolve discriminator"); 635 else if (expr.getInner() == null) 636 return focus; 637 else 638 return evaluateDefinition(expr.getInner(), sd, focus); 639 } 640 641 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 642 switch (exp.getFunction()) { 643 case Empty: 644 return funcEmpty(context, focus, exp); 645 case Not: 646 return funcNot(context, focus, exp); 647 case Exists: 648 return funcExists(context, focus, exp); 649 case SubsetOf: 650 return funcSubsetOf(context, focus, exp); 651 case SupersetOf: 652 return funcSupersetOf(context, focus, exp); 653 case IsDistinct: 654 return funcIsDistinct(context, focus, exp); 655 case Distinct: 656 return funcDistinct(context, focus, exp); 657 case Count: 658 return funcCount(context, focus, exp); 659 case Where: 660 return funcWhere(context, focus, exp); 661 case Select: 662 return funcSelect(context, focus, exp); 663 case All: 664 return funcAll(context, focus, exp); 665 case Repeat: 666 return funcRepeat(context, focus, exp); 667 case Aggregate: 668 return funcAggregate(context, focus, exp); 669 case Item: 670 return funcItem(context, focus, exp); 671 case As: 672 return funcAs(context, focus, exp); 673 case OfType: 674 return funcAs(context, focus, exp); 675 case Type: 676 return funcType(context, focus, exp); 677 case Is: 678 return funcIs(context, focus, exp); 679 case Single: 680 return funcSingle(context, focus, exp); 681 case First: 682 return funcFirst(context, focus, exp); 683 case Last: 684 return funcLast(context, focus, exp); 685 case Tail: 686 return funcTail(context, focus, exp); 687 case Skip: 688 return funcSkip(context, focus, exp); 689 case Take: 690 return funcTake(context, focus, exp); 691 case Union: 692 return funcUnion(context, focus, exp); 693 case Combine: 694 return funcCombine(context, focus, exp); 695 case Intersect: 696 return funcIntersect(context, focus, exp); 697 case Exclude: 698 return funcExclude(context, focus, exp); 699 case Iif: 700 return funcIif(context, focus, exp); 701 case Lower: 702 return funcLower(context, focus, exp); 703 case Upper: 704 return funcUpper(context, focus, exp); 705 case ToChars: 706 return funcToChars(context, focus, exp); 707 case Substring: 708 return funcSubstring(context, focus, exp); 709 case StartsWith: 710 return funcStartsWith(context, focus, exp); 711 case EndsWith: 712 return funcEndsWith(context, focus, exp); 713 case Matches: 714 return funcMatches(context, focus, exp); 715 case ReplaceMatches: 716 return funcReplaceMatches(context, focus, exp); 717 case Contains: 718 return funcContains(context, focus, exp); 719 case Replace: 720 return funcReplace(context, focus, exp); 721 case Length: 722 return funcLength(context, focus, exp); 723 case Children: 724 return funcChildren(context, focus, exp); 725 case Descendants: 726 return funcDescendants(context, focus, exp); 727 case MemberOf: 728 return funcMemberOf(context, focus, exp); 729 case Trace: 730 return funcTrace(context, focus, exp); 731 case Today: 732 return funcToday(context, focus, exp); 733 case Now: 734 return funcNow(context, focus, exp); 735 case Resolve: 736 return funcResolve(context, focus, exp); 737 case Extension: 738 return funcExtension(context, focus, exp); 739 case HasValue: 740 return funcHasValue(context, focus, exp); 741 case AliasAs: 742 return funcAliasAs(context, focus, exp); 743 case Alias: 744 return funcAlias(context, focus, exp); 745 case HtmlChecks: 746 return funcHtmlChecks(context, focus, exp); 747 case ToInteger: 748 return funcToInteger(context, focus, exp); 749 case ToDecimal: 750 return funcToDecimal(context, focus, exp); 751 case ToString: 752 return funcToString(context, focus, exp); 753 case ToBoolean: 754 return funcToBoolean(context, focus, exp); 755 case ToQuantity: 756 return funcToQuantity(context, focus, exp); 757 case ToDateTime: 758 return funcToDateTime(context, focus, exp); 759 case ToTime: 760 return funcToTime(context, focus, exp); 761 case IsInteger: 762 return funcIsInteger(context, focus, exp); 763 case IsDecimal: 764 return funcIsDecimal(context, focus, exp); 765 case IsString: 766 return funcIsString(context, focus, exp); 767 case IsBoolean: 768 return funcIsBoolean(context, focus, exp); 769 case IsQuantity: 770 return funcIsQuantity(context, focus, exp); 771 case IsDateTime: 772 return funcIsDateTime(context, focus, exp); 773 case IsTime: 774 return funcIsTime(context, focus, exp); 775 case Custom: { 776 List<List<Base>> params = new ArrayList<List<Base>>(); 777 for (ExpressionNode p : exp.getParameters()) 778 params.add(execute(context, focus, p, true)); 779 return hostServices.executeFunction(context.appInfo, exp.getName(), params); 780 } 781 default: 782 throw new Error("not Implemented yet"); 783 } 784 } 785 786 @SuppressWarnings("unchecked") 787 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { 788 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 789 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As) 790 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 791 else 792 for (ExpressionNode expr : exp.getParameters()) { 793 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) 794 paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); 795 else 796 paramTypes.add(executeType(context, focus, expr, true)); 797 } 798 switch (exp.getFunction()) { 799 case Empty: 800 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 801 case Not: 802 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 803 case Exists: 804 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 805 case SubsetOf: { 806 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 807 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 808 } 809 case SupersetOf: { 810 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 811 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 812 } 813 case IsDistinct: 814 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 815 case Distinct: 816 return focus; 817 case Count: 818 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 819 case Where: 820 return focus; 821 case Select: 822 return anything(focus.getCollectionStatus()); 823 case All: 824 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 825 case Repeat: 826 return anything(focus.getCollectionStatus()); 827 case Aggregate: 828 return anything(focus.getCollectionStatus()); 829 case Item: { 830 checkOrdered(focus, "item"); 831 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 832 return focus; 833 } 834 case As: { 835 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 836 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 837 } 838 case OfType: { 839 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 840 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 841 } 842 case Type: { 843 boolean s = false; 844 boolean c = false; 845 for (ProfiledType pt : focus.getProfiledTypes()) { 846 s = s || pt.isSystemType(); 847 c = c || !pt.isSystemType(); 848 } 849 if (s && c) 850 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); 851 else if (s) 852 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); 853 else 854 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); 855 } 856 case Is: { 857 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 858 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 859 } 860 case Single: 861 return focus.toSingleton(); 862 case First: { 863 checkOrdered(focus, "first"); 864 return focus.toSingleton(); 865 } 866 case Last: { 867 checkOrdered(focus, "last"); 868 return focus.toSingleton(); 869 } 870 case Tail: { 871 checkOrdered(focus, "tail"); 872 return focus; 873 } 874 case Skip: { 875 checkOrdered(focus, "skip"); 876 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 877 return focus; 878 } 879 case Take: { 880 checkOrdered(focus, "take"); 881 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 882 return focus; 883 } 884 case Union: { 885 return focus.union(paramTypes.get(0)); 886 } 887 case Combine: { 888 return focus.union(paramTypes.get(0)); 889 } 890 case Intersect: { 891 return focus.intersect(paramTypes.get(0)); 892 } 893 case Exclude: { 894 return focus; 895 } 896 case Iif: { 897 TypeDetails types = new TypeDetails(null); 898 types.update(paramTypes.get(0)); 899 if (paramTypes.size() > 1) 900 types.update(paramTypes.get(1)); 901 return types; 902 } 903 case Lower: { 904 checkContextString(focus, "lower"); 905 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 906 } 907 case Upper: { 908 checkContextString(focus, "upper"); 909 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 910 } 911 case ToChars: { 912 checkContextString(focus, "toChars"); 913 return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 914 } 915 case Substring: { 916 checkContextString(focus, "subString"); 917 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 918 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 919 } 920 case StartsWith: { 921 checkContextString(focus, "startsWith"); 922 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 923 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 924 } 925 case EndsWith: { 926 checkContextString(focus, "endsWith"); 927 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 928 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 929 } 930 case Matches: { 931 checkContextString(focus, "matches"); 932 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 933 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 934 } 935 case ReplaceMatches: { 936 checkContextString(focus, "replaceMatches"); 937 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 938 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 939 } 940 case Contains: { 941 checkContextString(focus, "contains"); 942 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 943 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 944 } 945 case Replace: { 946 checkContextString(focus, "replace"); 947 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 948 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 949 } 950 case Length: { 951 checkContextPrimitive(focus, "length", false); 952 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 953 } 954 case Children: 955 return childTypes(focus, "*"); 956 case Descendants: 957 return childTypes(focus, "**"); 958 case MemberOf: { 959 checkContextCoded(focus, "memberOf"); 960 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 961 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 962 } 963 case Trace: { 964 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 965 return focus; 966 } 967 case Today: 968 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 969 case Now: 970 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 971 case Resolve: { 972 checkContextReference(focus, "resolve"); 973 return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 974 } 975 case Extension: { 976 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 977 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 978 } 979 case HasValue: 980 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 981 case HtmlChecks: 982 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 983 case Alias: 984 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 985 return anything(CollectionStatus.SINGLETON); 986 case AliasAs: 987 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 988 return focus; 989 case ToInteger: { 990 checkContextPrimitive(focus, "toInteger", true); 991 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 992 } 993 case ToDecimal: { 994 checkContextPrimitive(focus, "toDecimal", true); 995 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 996 } 997 case ToString: { 998 checkContextPrimitive(focus, "toString", true); 999 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 1000 } 1001 case ToQuantity: { 1002 checkContextPrimitive(focus, "toQuantity", true); 1003 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 1004 } 1005 case ToBoolean: { 1006 checkContextPrimitive(focus, "toBoolean", false); 1007 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1008 } 1009 case ToDateTime: { 1010 checkContextPrimitive(focus, "toBoolean", false); 1011 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 1012 } 1013 case ToTime: { 1014 checkContextPrimitive(focus, "toBoolean", false); 1015 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 1016 } 1017 case IsString: 1018 case IsQuantity: { 1019 checkContextPrimitive(focus, exp.getFunction().toCode(), true); 1020 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1021 } 1022 case IsInteger: 1023 case IsDecimal: 1024 case IsDateTime: 1025 case IsTime: 1026 case IsBoolean: { 1027 checkContextPrimitive(focus, exp.getFunction().toCode(), false); 1028 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1029 } 1030 case Custom: { 1031 return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); 1032 } 1033 default: 1034 break; 1035 } 1036 throw new Error("not Implemented yet"); 1037 } 1038 1039 /** 1040 * evaluate a path and return true or false (e.g. for an invariant) 1041 * 1042 * @param base - the object against which the path is being evaluated 1043 * @param path - the FHIR Path statement to use 1044 * @return 1045 * @throws FHIRException 1046 * @ 1047 */ 1048 public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException { 1049 return convertToBoolean(evaluate(null, resource, base, path)); 1050 } 1051 1052 /** 1053 * evaluate a path and return true or false (e.g. for an invariant) 1054 * 1055 * @param base - the object against which the path is being evaluated 1056 * @return 1057 * @throws FHIRException 1058 * @ 1059 */ 1060 public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException { 1061 return convertToBoolean(evaluate(null, resource, base, node)); 1062 } 1063 1064 /** 1065 * evaluate a path and return true or false (e.g. for an invariant) 1066 * 1067 * @param appInfo - application context 1068 * @param base - the object against which the path is being evaluated 1069 * @return 1070 * @throws FHIRException 1071 * @ 1072 */ 1073 public boolean evaluateToBoolean(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException { 1074 return convertToBoolean(evaluate(appInfo, resource, base, node)); 1075 } 1076 1077 /** 1078 * evaluate a path and return true or false (e.g. for an invariant) 1079 * 1080 * @param base - the object against which the path is being evaluated 1081 * @return 1082 * @throws FHIRException 1083 * @ 1084 */ 1085 public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException { 1086 return convertToBoolean(evaluate(null, resource, base, node)); 1087 } 1088 1089 // procedure CheckParamCount(c : integer); 1090 // begin 1091 // if exp.Parameters.Count <> c then 1092 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 1093 // end; 1094 1095 /** 1096 * evaluate a path and a string containing the outcome (for display) 1097 * 1098 * @param base - the object against which the path is being evaluated 1099 * @param path - the FHIR Path statement to use 1100 * @return 1101 * @throws FHIRException 1102 * @ 1103 */ 1104 public String evaluateToString(Base base, String path) throws FHIRException { 1105 return convertToString(evaluate(base, path)); 1106 } 1107 1108 public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException { 1109 return convertToString(evaluate(appInfo, resource, base, node)); 1110 } 1111 1112 private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException { 1113// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 1114 List<Base> work = new ArrayList<Base>(); 1115 switch (exp.getKind()) { 1116 case Name: 1117 if (atEntry && exp.getName().equals("$this")) 1118 work.add(context.getThisItem()); 1119 else if (atEntry && exp.getName().equals("$total")) 1120 work.addAll(context.getTotal()); 1121 else 1122 for (Base item : focus) { 1123 List<Base> outcome = execute(context, item, exp, atEntry); 1124 for (Base base : outcome) 1125 if (base != null) 1126 work.add(base); 1127 } 1128 break; 1129 case Function: 1130 List<Base> work2 = evaluateFunction(context, focus, exp); 1131 work.addAll(work2); 1132 break; 1133 case Constant: 1134 Base b = resolveConstant(context, exp.getConstant()); 1135 if (b != null) 1136 work.add(b); 1137 break; 1138 case Group: 1139 work2 = execute(context, focus, exp.getGroup(), atEntry); 1140 work.addAll(work2); 1141 } 1142 1143 if (exp.getInner() != null) 1144 work = execute(context, work, exp.getInner(), false); 1145 1146 if (exp.isProximal() && exp.getOperation() != null) { 1147 ExpressionNode next = exp.getOpNext(); 1148 ExpressionNode last = exp; 1149 while (next != null) { 1150 List<Base> work2 = preOperate(work, last.getOperation()); 1151 if (work2 != null) 1152 work = work2; 1153 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 1154 work2 = executeTypeName(context, focus, next, false); 1155 work = operate(work, last.getOperation(), work2); 1156 } else { 1157 work2 = execute(context, focus, next, true); 1158 work = operate(work, last.getOperation(), work2); 1159// System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); 1160 } 1161 last = next; 1162 next = next.getOpNext(); 1163 } 1164 } 1165// System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); 1166 return work; 1167 } 1168 1169 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 1170 List<Base> result = new ArrayList<Base>(); 1171 if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 1172 if (item.isResource() && item.fhirType().equals(exp.getName())) 1173 result.add(item); 1174 } else 1175 getChildrenByName(item, exp.getName(), result); 1176 if (result.size() == 0 && atEntry && context.appInfo != null) { 1177 // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. 1178 // (if the name does match, and the user wants to get the constant value, they'll have to try harder... 1179 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName()); 1180 if (temp != null) { 1181 result.add(temp); 1182 } 1183 } 1184 return result; 1185 } 1186 1187 private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException { 1188 if (hostServices == null) 1189 throw new PathEngineException("Unable to resolve context reference since no host services are provided"); 1190 return hostServices.resolveConstantType(context.appInfo, name); 1191 } 1192 1193 private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1194 TypeDetails result = new TypeDetails(null); 1195 switch (exp.getKind()) { 1196 case Name: 1197 if (atEntry && exp.getName().equals("$this")) 1198 result.update(context.getThisItem()); 1199 else if (atEntry && exp.getName().equals("$total")) 1200 result.update(anything(CollectionStatus.UNORDERED)); 1201 else if (atEntry && focus == null) 1202 result.update(executeContextType(context, exp.getName())); 1203 else { 1204 for (String s : focus.getTypes()) { 1205 result.update(executeType(s, exp, atEntry)); 1206 } 1207 if (result.hasNoTypes()) 1208 throw new PathEngineException("The name " + exp.getName() + " is not valid for any of the possible types: " + focus.describe()); 1209 } 1210 break; 1211 case Function: 1212 result.update(evaluateFunctionType(context, focus, exp)); 1213 break; 1214 case Constant: 1215 result.update(resolveConstantType(context, exp.getConstant())); 1216 break; 1217 case Group: 1218 result.update(executeType(context, focus, exp.getGroup(), atEntry)); 1219 } 1220 exp.setTypes(result); 1221 1222 if (exp.getInner() != null) { 1223 result = executeType(context, result, exp.getInner(), false); 1224 } 1225 1226 if (exp.isProximal() && exp.getOperation() != null) { 1227 ExpressionNode next = exp.getOpNext(); 1228 ExpressionNode last = exp; 1229 while (next != null) { 1230 TypeDetails work; 1231 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) 1232 work = executeTypeName(context, focus, next, atEntry); 1233 else 1234 work = executeType(context, focus, next, atEntry); 1235 result = operateTypes(result, last.getOperation(), work); 1236 last = next; 1237 next = next.getOpNext(); 1238 } 1239 exp.setOpTypes(result); 1240 } 1241 return result; 1242 } 1243 1244 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1245 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up 1246 return new TypeDetails(CollectionStatus.SINGLETON, type); 1247 TypeDetails result = new TypeDetails(null); 1248 getChildTypesByName(type, exp.getName(), result); 1249 return result; 1250 } 1251 1252 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 1253 List<Base> result = new ArrayList<Base>(); 1254 result.add(new StringType(next.getName())); 1255 return result; 1256 } 1257 1258 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1259 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 1260 } 1261 1262 private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException { 1263 if (ed.getType().size() == 0) 1264 throw new DefinitionException("Error in discriminator at " + ed.getId() + ": no children, no type"); 1265 if (ed.getType().size() > 1) 1266 throw new DefinitionException("Error in discriminator at " + ed.getId() + ": no children, multiple types"); 1267 if (ed.getType().get(0).getProfile().size() > 1) 1268 throw new DefinitionException("Error in discriminator at " + ed.getId() + ": no children, multiple type profiles"); 1269 if (ed.hasSlicing()) 1270 throw new DefinitionException("Error in discriminator at " + ed.getId() + ": slicing found"); 1271 if (ed.getType().get(0).hasProfile()) 1272 return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); 1273 else 1274 return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode())); 1275 } 1276 1277 public String forLog() { 1278 if (log.length() > 0) 1279 return " (" + log.toString() + ")"; 1280 else 1281 return ""; 1282 } 1283 1284 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1285 List<Base> total = new ArrayList<Base>(); 1286 if (exp.parameterCount() > 1) 1287 total = execute(context, focus, exp.getParameters().get(1), false); 1288 1289 List<Base> pc = new ArrayList<Base>(); 1290 for (Base item : focus) { 1291 ExecutionContext c = changeThis(context, item); 1292 c.total = total; 1293 total = execute(c, pc, exp.getParameters().get(0), true); 1294 } 1295 return total; 1296 } 1297 1298 private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1299 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 1300 String name = nl.get(0).primitiveValue(); 1301 List<Base> res = new ArrayList<Base>(); 1302 Base b = context.getAlias(name); 1303 if (b != null) 1304 res.add(b); 1305 return res; 1306 } 1307 1308 private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1309 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 1310 String name = nl.get(0).primitiveValue(); 1311 context.addAlias(name, focus); 1312 return focus; 1313 } 1314 1315 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1316 if (exp.getParameters().size() == 1) { 1317 List<Base> result = new ArrayList<Base>(); 1318 List<Base> pc = new ArrayList<Base>(); 1319 boolean all = true; 1320 for (Base item : focus) { 1321 pc.clear(); 1322 pc.add(item); 1323 if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) { 1324 all = false; 1325 break; 1326 } 1327 } 1328 result.add(new BooleanType(all).noExtensions()); 1329 return result; 1330 } else {// (exp.getParameters().size() == 0) { 1331 List<Base> result = new ArrayList<Base>(); 1332 boolean all = true; 1333 for (Base item : focus) { 1334 boolean v = false; 1335 if (item instanceof BooleanType) { 1336 v = ((BooleanType) item).booleanValue(); 1337 } else 1338 v = item != null; 1339 if (!v) { 1340 all = false; 1341 break; 1342 } 1343 } 1344 result.add(new BooleanType(all).noExtensions()); 1345 return result; 1346 } 1347 } 1348 1349 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1350 List<Base> result = new ArrayList<Base>(); 1351 String tn = exp.getParameters().get(0).getName(); 1352 for (Base b : focus) 1353 if (b.hasType(tn)) 1354 result.add(b); 1355 return result; 1356 } 1357 1358 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1359 List<Base> result = new ArrayList<Base>(); 1360 for (Base b : focus) 1361 getChildrenByName(b, "*", result); 1362 return result; 1363 } 1364 1365 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1366 List<Base> result = new ArrayList<Base>(); 1367 for (Base item : focus) { 1368 result.add(item); 1369 } 1370 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 1371 result.add(item); 1372 } 1373 return result; 1374 } 1375 1376 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1377 List<Base> result = new ArrayList<Base>(); 1378 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1379 1380 if (focus.size() == 1 && !Utilities.noString(sw)) { 1381 String st = convertToString(focus.get(0)); 1382 if (Utilities.noString(st)) 1383 result.add(new BooleanType(false).noExtensions()); 1384 else 1385 result.add(new BooleanType(st.contains(sw)).noExtensions()); 1386 } else 1387 result.add(new BooleanType(false).noExtensions()); 1388 return result; 1389 } 1390 1391 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1392 List<Base> result = new ArrayList<Base>(); 1393 result.add(new IntegerType(focus.size()).noExtensions()); 1394 return result; 1395 } 1396 1397 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1398 List<Base> result = new ArrayList<Base>(); 1399 List<Base> current = new ArrayList<Base>(); 1400 current.addAll(focus); 1401 List<Base> added = new ArrayList<Base>(); 1402 boolean more = true; 1403 while (more) { 1404 added.clear(); 1405 for (Base item : current) { 1406 getChildrenByName(item, "*", added); 1407 } 1408 more = !added.isEmpty(); 1409 result.addAll(added); 1410 current.clear(); 1411 current.addAll(added); 1412 } 1413 return result; 1414 } 1415 1416 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1417 if (focus.size() <= 1) 1418 return focus; 1419 1420 List<Base> result = new ArrayList<Base>(); 1421 for (int i = 0; i < focus.size(); i++) { 1422 boolean found = false; 1423 for (int j = i + 1; j < focus.size(); j++) { 1424 if (doEquals(focus.get(j), focus.get(i))) { 1425 found = true; 1426 break; 1427 } 1428 } 1429 if (!found) 1430 result.add(focus.get(i)); 1431 } 1432 return result; 1433 } 1434 1435 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1436 List<Base> result = new ArrayList<Base>(); 1437 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 1438 return result; 1439 } 1440 1441 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1442 List<Base> result = new ArrayList<Base>(); 1443 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1444 1445 if (focus.size() == 1 && !Utilities.noString(sw)) 1446 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 1447 else 1448 result.add(new BooleanType(false).noExtensions()); 1449 return result; 1450 } 1451 1452 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1453 List<Base> result = new ArrayList<Base>(); 1454 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 1455 1456 for (Base item : focus) { 1457 if (!doContains(result, item) && !doContains(other, item)) 1458 result.add(item); 1459 } 1460 return result; 1461 } 1462 1463 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1464 List<Base> result = new ArrayList<Base>(); 1465 result.add(new BooleanType(!ElementUtil.isEmpty(focus)).noExtensions()); 1466 return result; 1467 } 1468 1469 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1470 List<Base> result = new ArrayList<Base>(); 1471 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 1472 String url = nl.get(0).primitiveValue(); 1473 1474 for (Base item : focus) { 1475 List<Base> ext = new ArrayList<Base>(); 1476 getChildrenByName(item, "extension", ext); 1477 getChildrenByName(item, "modifierExtension", ext); 1478 for (Base ex : ext) { 1479 List<Base> vl = new ArrayList<Base>(); 1480 getChildrenByName(ex, "url", vl); 1481 if (convertToString(vl).equals(url)) 1482 result.add(ex); 1483 } 1484 } 1485 return result; 1486 } 1487 1488 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1489 List<Base> result = new ArrayList<Base>(); 1490 if (focus.size() > 0) 1491 result.add(focus.get(0)); 1492 return result; 1493 } 1494 1495 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1496 List<Base> result = new ArrayList<Base>(); 1497 if (focus.size() == 1) { 1498 String s = convertToString(focus.get(0)); 1499 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 1500 } 1501 return result; 1502 } 1503 1504 private List<Base> funcHtmlChecks(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1505 // todo: actually check the HTML 1506 return makeBoolean(true); 1507 } 1508 1509 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1510 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 1511 Boolean v = convertToBoolean(n1); 1512 1513 if (v) 1514 return execute(context, focus, exp.getParameters().get(1), true); 1515 else if (exp.getParameters().size() < 3) 1516 return new ArrayList<Base>(); 1517 else 1518 return execute(context, focus, exp.getParameters().get(2), true); 1519 } 1520 1521 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1522 List<Base> result = new ArrayList<Base>(); 1523 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 1524 1525 for (Base item : focus) { 1526 if (!doContains(result, item) && doContains(other, item)) 1527 result.add(item); 1528 } 1529 return result; 1530 } 1531 1532 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 1533 if (focus.size() == 0 || focus.size() > 1) 1534 return makeBoolean(false); 1535 String ns = null; 1536 String n = null; 1537 1538 ExpressionNode texp = exp.getParameters().get(0); 1539 if (texp.getKind() != Kind.Name) 1540 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 1541 if (texp.getInner() != null) { 1542 if (texp.getInner().getKind() != Kind.Name) 1543 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 1544 ns = texp.getName(); 1545 n = texp.getInner().getName(); 1546 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { 1547 ns = "System"; 1548 n = texp.getName(); 1549 } else { 1550 ns = "FHIR"; 1551 n = texp.getName(); 1552 } 1553 if (ns.equals("System")) { 1554 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) 1555 return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType()))); 1556 else 1557 return makeBoolean(false); 1558 } else if (ns.equals("FHIR")) { 1559 return makeBoolean(n.equals(focus.get(0).fhirType())); 1560 } else { 1561 return makeBoolean(false); 1562 } 1563 } 1564 1565 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1566 List<Base> result = new ArrayList<Base>(); 1567 if (focus.size() != 1) 1568 result.add(new BooleanType(false).noExtensions()); 1569 else if (focus.get(0) instanceof IntegerType && ((IntegerType) focus.get(0)).getValue() >= 0) 1570 result.add(new BooleanType(true).noExtensions()); 1571 else if (focus.get(0) instanceof BooleanType) 1572 result.add(new BooleanType(true).noExtensions()); 1573 else if (focus.get(0) instanceof StringType) 1574 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)), "true", "false")).noExtensions()); 1575 else 1576 result.add(new BooleanType(false).noExtensions()); 1577 return result; 1578 } 1579 1580 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1581 List<Base> result = new ArrayList<Base>(); 1582 if (focus.size() != 1) 1583 result.add(new BooleanType(false).noExtensions()); 1584 else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) 1585 result.add(new BooleanType(true).noExtensions()); 1586 else if (focus.get(0) instanceof StringType) 1587 result.add(new BooleanType((convertToString(focus.get(0)).matches 1588 ("([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()); 1589 else 1590 result.add(new BooleanType(false).noExtensions()); 1591 return result; 1592 } 1593 1594 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1595 List<Base> result = new ArrayList<Base>(); 1596 if (focus.size() != 1) 1597 result.add(new BooleanType(false).noExtensions()); 1598 else if (focus.get(0) instanceof IntegerType) 1599 result.add(new BooleanType(true).noExtensions()); 1600 else if (focus.get(0) instanceof BooleanType) 1601 result.add(new BooleanType(true).noExtensions()); 1602 else if (focus.get(0) instanceof DecimalType) 1603 result.add(new BooleanType(true).noExtensions()); 1604 else if (focus.get(0) instanceof StringType) 1605 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)))).noExtensions()); 1606 else 1607 result.add(new BooleanType(false).noExtensions()); 1608 return result; 1609 } 1610 1611 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1612 if (focus.size() <= 1) 1613 return makeBoolean(true); 1614 1615 boolean distinct = true; 1616 for (int i = 0; i < focus.size(); i++) { 1617 for (int j = i + 1; j < focus.size(); j++) { 1618 if (doEquals(focus.get(j), focus.get(i))) { 1619 distinct = false; 1620 break; 1621 } 1622 } 1623 } 1624 return makeBoolean(distinct); 1625 } 1626 1627 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1628 List<Base> result = new ArrayList<Base>(); 1629 if (focus.size() != 1) 1630 result.add(new BooleanType(false).noExtensions()); 1631 else if (focus.get(0) instanceof IntegerType) 1632 result.add(new BooleanType(true).noExtensions()); 1633 else if (focus.get(0) instanceof BooleanType) 1634 result.add(new BooleanType(true).noExtensions()); 1635 else if (focus.get(0) instanceof StringType) 1636 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 1637 else 1638 result.add(new BooleanType(false).noExtensions()); 1639 return result; 1640 } 1641 1642 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1643 List<Base> result = new ArrayList<Base>(); 1644 if (focus.size() != 1) 1645 result.add(new BooleanType(false).noExtensions()); 1646 else if (focus.get(0) instanceof IntegerType) 1647 result.add(new BooleanType(true).noExtensions()); 1648 else if (focus.get(0) instanceof DecimalType) 1649 result.add(new BooleanType(true).noExtensions()); 1650 else if (focus.get(0) instanceof Quantity) 1651 result.add(new BooleanType(true).noExtensions()); 1652 else if (focus.get(0) instanceof StringType) { 1653 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 1654 result.add(new BooleanType(q != null).noExtensions()); 1655 } else 1656 result.add(new BooleanType(false).noExtensions()); 1657 return result; 1658 } 1659 1660 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1661 List<Base> result = new ArrayList<Base>(); 1662 if (focus.size() != 1) 1663 result.add(new BooleanType(false).noExtensions()); 1664 else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) 1665 result.add(new BooleanType(true).noExtensions()); 1666 else 1667 result.add(new BooleanType(false).noExtensions()); 1668 return result; 1669 } 1670 1671 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1672 List<Base> result = new ArrayList<Base>(); 1673 if (focus.size() != 1) 1674 result.add(new BooleanType(false).noExtensions()); 1675 else if (focus.get(0) instanceof TimeType) 1676 result.add(new BooleanType(true).noExtensions()); 1677 else if (focus.get(0) instanceof StringType) 1678 result.add(new BooleanType((convertToString(focus.get(0)).matches 1679 ("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()); 1680 else 1681 result.add(new BooleanType(false).noExtensions()); 1682 return result; 1683 } 1684 1685 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1686 List<Base> result = new ArrayList<Base>(); 1687 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1688 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 1689 result.add(focus.get(Integer.parseInt(s))); 1690 return result; 1691 } 1692 1693 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1694 List<Base> result = new ArrayList<Base>(); 1695 if (focus.size() > 0) 1696 result.add(focus.get(focus.size() - 1)); 1697 return result; 1698 } 1699 1700 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1701 List<Base> result = new ArrayList<Base>(); 1702 if (focus.size() == 1) { 1703 String s = convertToString(focus.get(0)); 1704 result.add(new IntegerType(s.length()).noExtensions()); 1705 } 1706 return result; 1707 } 1708 1709 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1710 List<Base> result = new ArrayList<Base>(); 1711 if (focus.size() == 1) { 1712 String s = convertToString(focus.get(0)); 1713 if (!Utilities.noString(s)) 1714 result.add(new StringType(s.toLowerCase()).noExtensions()); 1715 } 1716 return result; 1717 } 1718 1719 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1720 List<Base> result = new ArrayList<Base>(); 1721 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1722 1723 if (focus.size() == 1 && !Utilities.noString(sw)) { 1724 String st = convertToString(focus.get(0)); 1725 if (Utilities.noString(st)) 1726 result.add(new BooleanType(false).noExtensions()); 1727 else 1728 result.add(new BooleanType(st.matches(sw)).noExtensions()); 1729 } else 1730 result.add(new BooleanType(false).noExtensions()); 1731 return result; 1732 } 1733 1734 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1735 throw new Error("not Implemented yet"); 1736 } 1737 1738 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1739 return makeBoolean(!convertToBoolean(focus)); 1740 } 1741 1742 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1743 List<Base> result = new ArrayList<Base>(); 1744 result.add(DateTimeType.now()); 1745 return result; 1746 } 1747 1748 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1749 List<Base> result = new ArrayList<Base>(); 1750 List<Base> current = new ArrayList<Base>(); 1751 current.addAll(focus); 1752 List<Base> added = new ArrayList<Base>(); 1753 boolean more = true; 1754 while (more) { 1755 added.clear(); 1756 List<Base> pc = new ArrayList<Base>(); 1757 for (Base item : current) { 1758 pc.clear(); 1759 pc.add(item); 1760 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 1761 } 1762 more = !added.isEmpty(); 1763 result.addAll(added); 1764 current.clear(); 1765 current.addAll(added); 1766 } 1767 return result; 1768 } 1769 1770 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException { 1771 List<Base> result = new ArrayList<Base>(); 1772 1773 if (focus.size() == 1) { 1774 String f = convertToString(focus.get(0)); 1775 1776 if (!Utilities.noString(f)) { 1777 1778 if (exp.getParameters().size() != 2) { 1779 1780 String t = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1781 String r = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 1782 1783 String n = f.replace(t, r); 1784 result.add(new StringType(n)); 1785 } else { 1786 throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size())); 1787 } 1788 } else { 1789 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item")); 1790 } 1791 } else { 1792 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size())); 1793 } 1794 return result; 1795 } 1796 1797 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1798 List<Base> result = new ArrayList<Base>(); 1799 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1800 1801 if (focus.size() == 1 && !Utilities.noString(sw)) 1802 result.add(new BooleanType(convertToString(focus.get(0)).contains(sw)).noExtensions()); 1803 else 1804 result.add(new BooleanType(false).noExtensions()); 1805 return result; 1806 } 1807 1808 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1809 List<Base> result = new ArrayList<Base>(); 1810 for (Base item : focus) { 1811 String s = convertToString(item); 1812 if (item.fhirType().equals("Reference")) { 1813 Property p = item.getChildByName("reference"); 1814 if (p != null && p.hasValues()) 1815 s = convertToString(p.getValues().get(0)); 1816 else 1817 s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) 1818 } 1819 if (item.fhirType().equals("canonical")) { 1820 s = item.primitiveValue(); 1821 } 1822 if (s != null) { 1823 Base res = null; 1824 if (s.startsWith("#")) { 1825 Property p = context.resource.getChildByName("contained"); 1826 for (Base c : p.getValues()) { 1827 if (s.equals(c.getIdBase())) { 1828 res = c; 1829 break; 1830 } 1831 } 1832 } else if (hostServices != null) { 1833 res = hostServices.resolveReference(context.appInfo, s); 1834 } 1835 if (res != null) 1836 result.add(res); 1837 } 1838 } 1839 1840 return result; 1841 } 1842 1843 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1844 List<Base> result = new ArrayList<Base>(); 1845 List<Base> pc = new ArrayList<Base>(); 1846 for (Base item : focus) { 1847 pc.clear(); 1848 pc.add(item); 1849 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 1850 } 1851 return result; 1852 } 1853 1854 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 1855 if (focus.size() == 1) 1856 return focus; 1857 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 1858 } 1859 1860 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1861 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 1862 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 1863 1864 List<Base> result = new ArrayList<Base>(); 1865 for (int i = i1; i < focus.size(); i++) 1866 result.add(focus.get(i)); 1867 return result; 1868 } 1869 1870 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1871 List<Base> result = new ArrayList<Base>(); 1872 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 1873 1874 if (focus.size() == 1 && !Utilities.noString(sw)) 1875 result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw)).noExtensions()); 1876 else 1877 result.add(new BooleanType(false).noExtensions()); 1878 return result; 1879 } 1880 1881 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1882 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 1883 1884 boolean valid = true; 1885 for (Base item : focus) { 1886 boolean found = false; 1887 for (Base t : target) { 1888 if (Base.compareDeep(item, t, false)) { 1889 found = true; 1890 break; 1891 } 1892 } 1893 if (!found) { 1894 valid = false; 1895 break; 1896 } 1897 } 1898 List<Base> result = new ArrayList<Base>(); 1899 result.add(new BooleanType(valid).noExtensions()); 1900 return result; 1901 } 1902 1903 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1904 List<Base> result = new ArrayList<Base>(); 1905 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 1906 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 1907 int i2 = -1; 1908 if (exp.parameterCount() == 2) { 1909 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 1910 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 1911 } 1912 1913 if (focus.size() == 1) { 1914 String sw = convertToString(focus.get(0)); 1915 String s; 1916 if (i1 < 0 || i1 >= sw.length()) 1917 return new ArrayList<Base>(); 1918 if (exp.parameterCount() == 2) 1919 s = sw.substring(i1, Math.min(sw.length(), i1 + i2)); 1920 else 1921 s = sw.substring(i1); 1922 if (!Utilities.noString(s)) 1923 result.add(new StringType(s).noExtensions()); 1924 } 1925 return result; 1926 } 1927 1928 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1929 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 1930 1931 boolean valid = true; 1932 for (Base item : target) { 1933 boolean found = false; 1934 for (Base t : focus) { 1935 if (Base.compareDeep(item, t, false)) { 1936 found = true; 1937 break; 1938 } 1939 } 1940 if (!found) { 1941 valid = false; 1942 break; 1943 } 1944 } 1945 List<Base> result = new ArrayList<Base>(); 1946 result.add(new BooleanType(valid).noExtensions()); 1947 return result; 1948 } 1949 1950 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1951 List<Base> result = new ArrayList<Base>(); 1952 for (int i = 1; i < focus.size(); i++) 1953 result.add(focus.get(i)); 1954 return result; 1955 } 1956 1957 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1958 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 1959 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 1960 1961 List<Base> result = new ArrayList<Base>(); 1962 for (int i = 0; i < Math.min(focus.size(), i1); i++) 1963 result.add(focus.get(i)); 1964 return result; 1965 } 1966 1967 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1968 List<Base> result = new ArrayList<Base>(); 1969 if (focus.size() == 1) { 1970 if (focus.get(0) instanceof BooleanType) 1971 result.add(focus.get(0)); 1972 else if (focus.get(0) instanceof IntegerType) 1973 result.add(new BooleanType(!focus.get(0).primitiveValue().equals("0")).noExtensions()); 1974 else if (focus.get(0) instanceof StringType) { 1975 if ("true".equals(focus.get(0).primitiveValue())) 1976 result.add(new BooleanType(true).noExtensions()); 1977 else if ("false".equals(focus.get(0).primitiveValue())) 1978 result.add(new BooleanType(false).noExtensions()); 1979 } 1980 } 1981 return result; 1982 } 1983 1984 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 1985 List<Base> result = new ArrayList<Base>(); 1986 if (focus.size() == 1) { 1987 String s = convertToString(focus.get(0)); 1988 for (char c : s.toCharArray()) 1989 result.add(new StringType(String.valueOf(c)).noExtensions()); 1990 } 1991 return result; 1992 } 1993 1994 // private boolean isPrimitiveType(String s) { 1995 // 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"); 1996 // } 1997 1998 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 1999// List<Base> result = new ArrayList<Base>(); 2000// result.add(new BooleanType(convertToBoolean(focus))); 2001// return result; 2002 throw new NotImplementedException("funcToDateTime is not implemented"); 2003 } 2004 2005 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2006 String s = convertToString(focus); 2007 List<Base> result = new ArrayList<Base>(); 2008 if (Utilities.isDecimal(s)) 2009 result.add(new DecimalType(s).noExtensions()); 2010 if ("true".equals(s)) 2011 result.add(new DecimalType(1).noExtensions()); 2012 if ("false".equals(s)) 2013 result.add(new DecimalType(0).noExtensions()); 2014 return result; 2015 } 2016 2017 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2018 String s = convertToString(focus); 2019 List<Base> result = new ArrayList<Base>(); 2020 if (Utilities.isInteger(s)) 2021 result.add(new IntegerType(s).noExtensions()); 2022 else if ("true".equals(s)) 2023 result.add(new IntegerType(1).noExtensions()); 2024 else if ("false".equals(s)) 2025 result.add(new IntegerType(0).noExtensions()); 2026 return result; 2027 } 2028 2029 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2030 List<Base> result = new ArrayList<Base>(); 2031 if (focus.size() == 1) { 2032 if (focus.get(0) instanceof Quantity) 2033 result.add(focus.get(0)); 2034 else if (focus.get(0) instanceof StringType) { 2035 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 2036 if (q != null) 2037 result.add(q.noExtensions()); 2038 } else if (focus.get(0) instanceof IntegerType) { 2039 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2040 } else if (focus.get(0) instanceof DecimalType) { 2041 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2042 } 2043 } 2044 return result; 2045 } 2046 2047 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2048 List<Base> result = new ArrayList<Base>(); 2049 result.add(new StringType(convertToString(focus)).noExtensions()); 2050 return result; 2051 } 2052 2053 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2054// List<Base> result = new ArrayList<Base>(); 2055// result.add(new BooleanType(convertToBoolean(focus))); 2056// return result; 2057 throw new NotImplementedException("funcToTime is not implemented"); 2058 } 2059 2060 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2061 List<Base> result = new ArrayList<Base>(); 2062 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 2063 return result; 2064 } 2065 2066 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2067 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2068 String name = nl.get(0).primitiveValue(); 2069 2070 log(name, focus); 2071 return focus; 2072 } 2073 2074 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2075 List<Base> result = new ArrayList<Base>(); 2076 for (Base item : focus) 2077 result.add(new ClassTypeInfo(item)); 2078 return result; 2079 } 2080 2081 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2082 List<Base> result = new ArrayList<Base>(); 2083 for (Base item : focus) { 2084 if (!doContains(result, item)) 2085 result.add(item); 2086 } 2087 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 2088 if (!doContains(result, item)) 2089 result.add(item); 2090 } 2091 return result; 2092 } 2093 2094 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2095 List<Base> result = new ArrayList<Base>(); 2096 if (focus.size() == 1) { 2097 String s = convertToString(focus.get(0)); 2098 if (!Utilities.noString(s)) 2099 result.add(new StringType(s.toUpperCase()).noExtensions()); 2100 } 2101 return result; 2102 } 2103 2104 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2105 List<Base> result = new ArrayList<Base>(); 2106 List<Base> pc = new ArrayList<Base>(); 2107 for (Base item : focus) { 2108 pc.clear(); 2109 pc.add(item); 2110 if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) 2111 result.add(item); 2112 } 2113 return result; 2114 } 2115 2116 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 2117 // work : boolean; 2118 // focus, node, group : ExpressionNode; 2119 2120 assert (start.isProximal()); 2121 2122 // is there anything to do? 2123 boolean work = false; 2124 ExpressionNode focus = start.getOpNext(); 2125 if (ops.contains(start.getOperation())) { 2126 while (focus != null && focus.getOperation() != null) { 2127 work = work || !ops.contains(focus.getOperation()); 2128 focus = focus.getOpNext(); 2129 } 2130 } else { 2131 while (focus != null && focus.getOperation() != null) { 2132 work = work || ops.contains(focus.getOperation()); 2133 focus = focus.getOpNext(); 2134 } 2135 } 2136 if (!work) 2137 return start; 2138 2139 // entry point: tricky 2140 ExpressionNode group; 2141 if (ops.contains(start.getOperation())) { 2142 group = newGroup(lexer, start); 2143 group.setProximal(true); 2144 focus = start; 2145 start = group; 2146 } else { 2147 ExpressionNode node = start; 2148 2149 focus = node.getOpNext(); 2150 while (!ops.contains(focus.getOperation())) { 2151 node = focus; 2152 focus = focus.getOpNext(); 2153 } 2154 group = newGroup(lexer, focus); 2155 node.setOpNext(group); 2156 } 2157 2158 // now, at this point: 2159 // group is the group we are adding to, it already has a .group property filled out. 2160 // focus points at the group.group 2161 do { 2162 // run until we find the end of the sequence 2163 while (ops.contains(focus.getOperation())) 2164 focus = focus.getOpNext(); 2165 if (focus.getOperation() != null) { 2166 group.setOperation(focus.getOperation()); 2167 group.setOpNext(focus.getOpNext()); 2168 focus.setOperation(null); 2169 focus.setOpNext(null); 2170 // now look for another sequence, and start it 2171 ExpressionNode node = group; 2172 focus = group.getOpNext(); 2173 if (focus != null) { 2174 while (focus != null && !ops.contains(focus.getOperation())) { 2175 node = focus; 2176 focus = focus.getOpNext(); 2177 } 2178 if (focus != null) { // && (focus.Operation in Ops) - must be true 2179 group = newGroup(lexer, focus); 2180 node.setOpNext(group); 2181 } 2182 } 2183 } 2184 } 2185 while (focus != null && focus.getOperation() != null); 2186 return start; 2187 } 2188 2189 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 2190 if (Utilities.noString(type)) 2191 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 2192 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) 2193 return; 2194 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 2195 getSimpleTypeChildTypesByName(name, result); 2196 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 2197 getClassInfoChildTypesByName(name, result); 2198 } else { 2199 String url = null; 2200 if (type.contains("#")) { 2201 url = type.substring(0, type.indexOf("#")); 2202 } else { 2203 url = type; 2204 } 2205 String tail = ""; 2206 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 2207 if (sd == null) 2208 throw new DefinitionException("Unknown type " + type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 2209 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 2210 ElementDefinitionMatch m = null; 2211 if (type.contains("#")) 2212 m = getElementDefinition(sd, type.substring(type.indexOf("#") + 1), false); 2213 if (m != null && hasDataType(m.definition)) { 2214 if (m.fixedType != null) { 2215 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType)); 2216 if (dt == null) 2217 throw new DefinitionException("unknown data type " + m.fixedType); 2218 sdl.add(dt); 2219 } else 2220 for (TypeRefComponent t : m.definition.getType()) { 2221 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode())); 2222 if (dt == null) 2223 throw new DefinitionException("unknown data type " + t.getCode()); 2224 sdl.add(dt); 2225 } 2226 } else { 2227 sdl.add(sd); 2228 if (type.contains("#")) { 2229 tail = type.substring(type.indexOf("#") + 1); 2230 tail = tail.substring(tail.indexOf(".")); 2231 } 2232 } 2233 2234 for (StructureDefinition sdi : sdl) { 2235 String path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "."; 2236 if (name.equals("**")) { 2237 assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); 2238 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2239 if (ed.getPath().startsWith(path)) 2240 for (TypeRefComponent t : ed.getType()) { 2241 if (t.hasCode() && t.getCodeElement().hasValue()) { 2242 String tn = null; 2243 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2244 tn = sdi.getType() + "#" + ed.getPath(); 2245 else 2246 tn = t.getCode(); 2247 if (t.getCode().equals("Resource")) { 2248 for (String rn : worker.getResourceNames()) { 2249 if (!result.hasType(worker, rn)) { 2250 getChildTypesByName(result.addType(rn), "**", result); 2251 } 2252 } 2253 } else if (!result.hasType(worker, tn)) { 2254 getChildTypesByName(result.addType(tn), "**", result); 2255 } 2256 } 2257 } 2258 } 2259 } else if (name.equals("*")) { 2260 assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); 2261 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2262 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 2263 for (TypeRefComponent t : ed.getType()) { 2264 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2265 result.addType(sdi.getType() + "#" + ed.getPath()); 2266 else if (t.getCode().equals("Resource")) 2267 result.addTypes(worker.getResourceNames()); 2268 else 2269 result.addType(t.getCode()); 2270 } 2271 } 2272 } else { 2273 path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "." + name; 2274 2275 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 2276 if (ed != null) { 2277 if (!Utilities.noString(ed.getFixedType())) 2278 result.addType(ed.getFixedType()); 2279 else 2280 for (TypeRefComponent t : ed.getDefinition().getType()) { 2281 if (Utilities.noString(t.getCode())) 2282 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 2283 2284 ProfiledType pt = null; 2285 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2286 pt = new ProfiledType(sdi.getUrl() + "#" + path); 2287 else if (t.getCode().equals("Resource")) 2288 result.addTypes(worker.getResourceNames()); 2289 else 2290 pt = new ProfiledType(t.getCode()); 2291 if (pt != null) { 2292 if (t.hasProfile()) 2293 pt.addProfiles(t.getProfile()); 2294 if (ed.getDefinition().hasBinding()) 2295 pt.addBinding(ed.getDefinition().getBinding()); 2296 result.addType(pt); 2297 } 2298 } 2299 } 2300 } 2301 } 2302 } 2303 } 2304 2305 /** 2306 * Given an item, return all the children that conform to the pattern described in name 2307 * <p> 2308 * Possible patterns: 2309 * - a simple name (which may be the base of a name with [] e.g. value[x]) 2310 * - a name with a type replacement e.g. valueCodeableConcept 2311 * - * which means all children 2312 * - ** which means all descendants 2313 * 2314 * @param item 2315 * @param name 2316 * @param result 2317 * @throws FHIRException 2318 */ 2319 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 2320 Base[] list = item.listChildrenByName(name, false); 2321 if (list != null) 2322 for (Base v : list) 2323 if (v != null) 2324 result.add(v); 2325 } 2326 2327 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 2328 if (name.equals("namespace")) 2329 result.addType(TypeDetails.FP_String); 2330 if (name.equals("name")) 2331 result.addType(TypeDetails.FP_String); 2332 } 2333 2334 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 2335 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2336 if (ed.getPath().equals(path)) { 2337 if (ed.hasContentReference()) { 2338 return getElementDefinitionById(sd, ed.getContentReference()); 2339 } else 2340 return new ElementDefinitionMatch(ed, null); 2341 } 2342 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) && path.length() == ed.getPath().length() - 3) 2343 return new ElementDefinitionMatch(ed, null); 2344 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) && path.length() > ed.getPath().length() - 3) { 2345 String s = Utilities.uncapitalize(path.substring(ed.getPath().length() - 3)); 2346 if (primitiveTypes.contains(s)) 2347 return new ElementDefinitionMatch(ed, s); 2348 else 2349 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length() - 3)); 2350 } 2351 if (ed.getPath().contains(".") && path.startsWith(ed.getPath() + ".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 2352 // now we walk into the type. 2353 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 2354 throw new PathEngineException("Internal typing issue...."); 2355 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode())); 2356 if (nsd == null) 2357 throw new PathEngineException("Unknown type " + ed.getType().get(0).getCode()); 2358 return getElementDefinition(nsd, nsd.getId() + path.substring(ed.getPath().length()), allowTypedName); 2359 } 2360 if (ed.hasContentReference() && path.startsWith(ed.getPath() + ".")) { 2361 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 2362 return getElementDefinition(sd, m.definition.getPath() + path.substring(ed.getPath().length()), allowTypedName); 2363 } 2364 } 2365 return null; 2366 } 2367 2368 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 2369 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2370 if (ref.equals("#" + ed.getId())) 2371 return new ElementDefinitionMatch(ed, null); 2372 } 2373 return null; 2374 } 2375 2376 public IEvaluationContext getHostServices() { 2377 return hostServices; 2378 } 2379 2380 public void setHostServices(IEvaluationContext constantResolver) { 2381 this.hostServices = constantResolver; 2382 } 2383 2384 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 2385 if (name.equals("namespace")) 2386 result.addType(TypeDetails.FP_String); 2387 if (name.equals("name")) 2388 result.addType(TypeDetails.FP_String); 2389 } 2390 2391 private boolean hasDataType(ElementDefinition ed) { 2392 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 2393 } 2394 2395 public boolean hasLog() { 2396 return log != null && log.length() > 0; 2397 } 2398 2399 private boolean hasType(ElementDefinition ed, String s) { 2400 for (TypeRefComponent t : ed.getType()) 2401 if (s.equalsIgnoreCase(t.getCode())) 2402 return true; 2403 return false; 2404 } 2405 2406 private String hashTail(String type) { 2407 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/") + 1); 2408 } 2409 2410 private boolean isAbstractType(List<TypeRefComponent> list) { 2411 return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 2412 } 2413 2414 private boolean isBoolean(List<Base> list, boolean b) { 2415 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 2416 } 2417 2418 private void log(String name, List<Base> contents) { 2419 if (hostServices == null || !hostServices.log(name, contents)) { 2420 if (log.length() > 0) 2421 log.append("; "); 2422 log.append(name); 2423 log.append(": "); 2424 boolean first = true; 2425 for (Base b : contents) { 2426 if (first) 2427 first = false; 2428 else 2429 log.append(","); 2430 log.append(convertToString(b)); 2431 } 2432 } 2433 } 2434 2435 private List<Base> makeBoolean(boolean b) { 2436 List<Base> res = new ArrayList<Base>(); 2437 res.add(new BooleanType(b).noExtensions()); 2438 return res; 2439 } 2440 2441 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 2442 ExpressionNode result = new ExpressionNode(lexer.nextId()); 2443 result.setKind(Kind.Group); 2444 result.setGroup(next); 2445 result.getGroup().setProximal(true); 2446 return result; 2447 } 2448 2449 private List<Base> opAnd(List<Base> left, List<Base> right) { 2450 if (left.isEmpty() && right.isEmpty()) 2451 return new ArrayList<Base>(); 2452 else if (isBoolean(left, false) || isBoolean(right, false)) 2453 return makeBoolean(false); 2454 else if (left.isEmpty() || right.isEmpty()) 2455 return new ArrayList<Base>(); 2456 else if (convertToBoolean(left) && convertToBoolean(right)) 2457 return makeBoolean(true); 2458 else 2459 return makeBoolean(false); 2460 } 2461 2462 private List<Base> opAs(List<Base> left, List<Base> right) { 2463 List<Base> result = new ArrayList<Base>(); 2464 if (left.size() != 1 || right.size() != 1) 2465 return result; 2466 else { 2467 String tn = convertToString(right); 2468 if (tn.equals(left.get(0).fhirType())) 2469 result.add(left.get(0)); 2470 } 2471 return result; 2472 } 2473 2474 private List<Base> opConcatenate(List<Base> left, List<Base> right) { 2475 List<Base> result = new ArrayList<Base>(); 2476 result.add(new StringType(convertToString(left) + convertToString((right)))); 2477 return result; 2478 } 2479 2480 private List<Base> opContains(List<Base> left, List<Base> right) { 2481 boolean ans = true; 2482 for (Base r : right) { 2483 boolean f = false; 2484 for (Base l : left) 2485 if (doEquals(l, r)) { 2486 f = true; 2487 break; 2488 } 2489 if (!f) { 2490 ans = false; 2491 break; 2492 } 2493 } 2494 return makeBoolean(ans); 2495 } 2496 2497 private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException { 2498 if (left.size() == 0) 2499 throw new PathEngineException("Error performing div: left operand has no value"); 2500 if (left.size() > 1) 2501 throw new PathEngineException("Error performing div: left operand has more than one value"); 2502 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2503 throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType())); 2504 if (right.size() == 0) 2505 throw new PathEngineException("Error performing div: right operand has no value"); 2506 if (right.size() > 1) 2507 throw new PathEngineException("Error performing div: right operand has more than one value"); 2508 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2509 throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType())); 2510 2511 List<Base> result = new ArrayList<Base>(); 2512 Base l = left.get(0); 2513 Base r = right.get(0); 2514 2515 if (l.hasType("integer") && r.hasType("integer")) 2516 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue()))); 2517 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2518 Decimal d1; 2519 try { 2520 d1 = new Decimal(l.primitiveValue()); 2521 Decimal d2 = new Decimal(r.primitiveValue()); 2522 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 2523 } catch (UcumException e) { 2524 throw new PathEngineException(e); 2525 } 2526 } else 2527 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())); 2528 return result; 2529 } 2530 2531 private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException { 2532 if (left.size() == 0) 2533 throw new PathEngineException("Error performing /: left operand has no value"); 2534 if (left.size() > 1) 2535 throw new PathEngineException("Error performing /: left operand has more than one value"); 2536 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2537 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2538 if (right.size() == 0) 2539 throw new PathEngineException("Error performing /: right operand has no value"); 2540 if (right.size() > 1) 2541 throw new PathEngineException("Error performing /: right operand has more than one value"); 2542 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2543 throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType())); 2544 2545 List<Base> result = new ArrayList<Base>(); 2546 Base l = left.get(0); 2547 Base r = right.get(0); 2548 2549 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 2550 Decimal d1; 2551 try { 2552 d1 = new Decimal(l.primitiveValue()); 2553 Decimal d2 = new Decimal(r.primitiveValue()); 2554 result.add(new DecimalType(d1.divide(d2).asDecimal())); 2555 } catch (UcumException e) { 2556 throw new PathEngineException(e); 2557 } 2558 } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2559 Pair pl = qtyToPair((Quantity) l); 2560 Pair pr = qtyToPair((Quantity) r); 2561 Pair p; 2562 try { 2563 p = worker.getUcumService().multiply(pl, pr); 2564 result.add(pairToQty(p)); 2565 } catch (UcumException e) { 2566 throw new PathEngineException(e.getMessage(), e); 2567 } 2568 } else 2569 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())); 2570 return result; 2571 } 2572 2573 private List<Base> opEquals(List<Base> left, List<Base> right) { 2574 if (left.size() != right.size()) 2575 return makeBoolean(false); 2576 2577 boolean res = true; 2578 for (int i = 0; i < left.size(); i++) { 2579 if (!doEquals(left.get(i), right.get(i))) { 2580 res = false; 2581 break; 2582 } 2583 } 2584 return makeBoolean(res); 2585 } 2586 2587 private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 2588 if (left.size() != right.size()) 2589 return makeBoolean(false); 2590 2591 boolean res = true; 2592 for (int i = 0; i < left.size(); i++) { 2593 boolean found = false; 2594 for (int j = 0; j < right.size(); j++) { 2595 if (doEquivalent(left.get(i), right.get(j))) { 2596 found = true; 2597 break; 2598 } 2599 } 2600 if (!found) { 2601 res = false; 2602 break; 2603 } 2604 } 2605 return makeBoolean(res); 2606 } 2607 2608 private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException { 2609 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2610 Base l = left.get(0); 2611 Base r = right.get(0); 2612 if (l.hasType("string") && r.hasType("string")) 2613 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 2614 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 2615 return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); 2616 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 2617 return makeBoolean(compareDateTimeElements(l, r, false) > 0); 2618 else if ((l.hasType("time")) && (r.hasType("time"))) 2619 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 2620 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity")) { 2621 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 2622 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 2623 if (Base.compareDeep(lUnit, rUnit, true)) { 2624 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 2625 } else { 2626 if (worker.getUcumService() == null) 2627 return makeBoolean(false); 2628 else { 2629 List<Base> dl = new ArrayList<Base>(); 2630 dl.add(qtyToCanonical((Quantity) left.get(0))); 2631 List<Base> dr = new ArrayList<Base>(); 2632 dr.add(qtyToCanonical((Quantity) right.get(0))); 2633 return opGreater(dl, dr); 2634 } 2635 } 2636 } 2637 return new ArrayList<Base>(); 2638 } 2639 2640 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException { 2641 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2642 Base l = left.get(0); 2643 Base r = right.get(0); 2644 if (l.hasType("string") && r.hasType("string")) 2645 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 2646 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 2647 return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); 2648 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 2649 return makeBoolean(compareDateTimeElements(l, r, false) >= 0); 2650 else if ((l.hasType("time")) && (r.hasType("time"))) 2651 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 2652 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity")) { 2653 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 2654 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 2655 if (Base.compareDeep(lUnit, rUnit, true)) { 2656 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 2657 } else { 2658 if (worker.getUcumService() == null) 2659 return makeBoolean(false); 2660 else { 2661 List<Base> dl = new ArrayList<Base>(); 2662 dl.add(qtyToCanonical((Quantity) left.get(0))); 2663 List<Base> dr = new ArrayList<Base>(); 2664 dr.add(qtyToCanonical((Quantity) right.get(0))); 2665 return opGreaterOrEqual(dl, dr); 2666 } 2667 } 2668 } 2669 return new ArrayList<Base>(); 2670 } 2671 2672 private List<Base> opImplies(List<Base> left, List<Base> right) { 2673 if (!convertToBoolean(left)) 2674 return makeBoolean(true); 2675 else if (right.size() == 0) 2676 return new ArrayList<Base>(); 2677 else 2678 return makeBoolean(convertToBoolean(right)); 2679 } 2680 2681 private List<Base> opIn(List<Base> left, List<Base> right) throws FHIRException { 2682 boolean ans = true; 2683 for (Base l : left) { 2684 boolean f = false; 2685 for (Base r : right) 2686 if (doEquals(l, r)) { 2687 f = true; 2688 break; 2689 } 2690 if (!f) { 2691 ans = false; 2692 break; 2693 } 2694 } 2695 return makeBoolean(ans); 2696 } 2697 2698 private List<Base> opIs(List<Base> left, List<Base> right) { 2699 List<Base> result = new ArrayList<Base>(); 2700 if (left.size() != 1 || right.size() != 1) 2701 result.add(new BooleanType(false).noExtensions()); 2702 else { 2703 String tn = convertToString(right); 2704 if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element) 2705 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 2706 else if ((left.get(0) instanceof Element) || ((Element) left.get(0)).isDisallowExtensions()) 2707 result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn)).noExtensions()); 2708 else 2709 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 2710 } 2711 return result; 2712 } 2713 2714 private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException { 2715 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2716 Base l = left.get(0); 2717 Base r = right.get(0); 2718 if (l.hasType("string") && r.hasType("string")) 2719 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 2720 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 2721 return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); 2722 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 2723 return makeBoolean(compareDateTimeElements(l, r, false) <= 0); 2724 else if ((l.hasType("time")) && (r.hasType("time"))) 2725 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 2726 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity")) { 2727 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 2728 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 2729 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 2730 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 2731 if ((lunit == null && runit == null) || lunit.equals(runit)) { 2732 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 2733 } else { 2734 if (worker.getUcumService() == null) 2735 return makeBoolean(false); 2736 else { 2737 List<Base> dl = new ArrayList<Base>(); 2738 dl.add(qtyToCanonical((Quantity) left.get(0))); 2739 List<Base> dr = new ArrayList<Base>(); 2740 dr.add(qtyToCanonical((Quantity) right.get(0))); 2741 return opLessOrEqual(dl, dr); 2742 } 2743 } 2744 } 2745 return new ArrayList<Base>(); 2746 } 2747 2748 private List<Base> opLessThen(List<Base> left, List<Base> right) throws FHIRException { 2749 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2750 Base l = left.get(0); 2751 Base r = right.get(0); 2752 if (l.hasType("string") && r.hasType("string")) 2753 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 2754 else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 2755 return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); 2756 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 2757 return makeBoolean(compareDateTimeElements(l, r, false) < 0); 2758 else if ((l.hasType("time")) && (r.hasType("time"))) 2759 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 2760 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity")) { 2761 List<Base> lUnit = left.get(0).listChildrenByName("code"); 2762 List<Base> rUnit = right.get(0).listChildrenByName("code"); 2763 if (Base.compareDeep(lUnit, rUnit, true)) { 2764 return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 2765 } else { 2766 if (worker.getUcumService() == null) 2767 return makeBoolean(false); 2768 else { 2769 List<Base> dl = new ArrayList<Base>(); 2770 dl.add(qtyToCanonical((Quantity) left.get(0))); 2771 List<Base> dr = new ArrayList<Base>(); 2772 dr.add(qtyToCanonical((Quantity) right.get(0))); 2773 return opLessThen(dl, dr); 2774 } 2775 } 2776 } 2777 return new ArrayList<Base>(); 2778 } 2779 2780 private List<Base> opMemberOf(List<Base> left, List<Base> right) throws FHIRException { 2781 boolean ans = false; 2782 ValueSet vs = worker.fetchResource(ValueSet.class, right.get(0).primitiveValue()); 2783 if (vs != null) { 2784 for (Base l : left) { 2785 if (l.fhirType().equals("code")) { 2786 if (worker.validateCode(l.castToCoding(l), vs).isOk()) 2787 ans = true; 2788 } else if (l.fhirType().equals("Coding")) { 2789 if (worker.validateCode(l.castToCoding(l), vs).isOk()) 2790 ans = true; 2791 } else if (l.fhirType().equals("CodeableConcept")) { 2792 if (worker.validateCode(l.castToCodeableConcept(l), vs).isOk()) 2793 ans = true; 2794 } 2795 } 2796 } 2797 return makeBoolean(ans); 2798 } 2799 2800 private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException { 2801 if (left.size() == 0) 2802 throw new PathEngineException("Error performing -: left operand has no value"); 2803 if (left.size() > 1) 2804 throw new PathEngineException("Error performing -: left operand has more than one value"); 2805 if (!left.get(0).isPrimitive()) 2806 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2807 if (right.size() == 0) 2808 throw new PathEngineException("Error performing -: right operand has no value"); 2809 if (right.size() > 1) 2810 throw new PathEngineException("Error performing -: right operand has more than one value"); 2811 if (!right.get(0).isPrimitive()) 2812 throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType())); 2813 2814 List<Base> result = new ArrayList<Base>(); 2815 Base l = left.get(0); 2816 Base r = right.get(0); 2817 2818 if (l.hasType("integer") && r.hasType("integer")) 2819 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 2820 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 2821 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 2822 else 2823 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())); 2824 return result; 2825 } 2826 2827 private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException { 2828 if (left.size() == 0) 2829 throw new PathEngineException("Error performing mod: left operand has no value"); 2830 if (left.size() > 1) 2831 throw new PathEngineException("Error performing mod: left operand has more than one value"); 2832 if (!left.get(0).isPrimitive()) 2833 throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType())); 2834 if (right.size() == 0) 2835 throw new PathEngineException("Error performing mod: right operand has no value"); 2836 if (right.size() > 1) 2837 throw new PathEngineException("Error performing mod: right operand has more than one value"); 2838 if (!right.get(0).isPrimitive()) 2839 throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType())); 2840 2841 List<Base> result = new ArrayList<Base>(); 2842 Base l = left.get(0); 2843 Base r = right.get(0); 2844 2845 if (l.hasType("integer") && r.hasType("integer")) 2846 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue()))); 2847 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2848 Decimal d1; 2849 try { 2850 d1 = new Decimal(l.primitiveValue()); 2851 Decimal d2 = new Decimal(r.primitiveValue()); 2852 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 2853 } catch (UcumException e) { 2854 throw new PathEngineException(e); 2855 } 2856 } else 2857 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())); 2858 return result; 2859 } 2860 2861 private List<Base> opNotEquals(List<Base> left, List<Base> right) { 2862 if (left.size() != right.size()) 2863 return makeBoolean(true); 2864 2865 boolean res = true; 2866 for (int i = 0; i < left.size(); i++) { 2867 if (!doEquals(left.get(i), right.get(i))) { 2868 res = false; 2869 break; 2870 } 2871 } 2872 return makeBoolean(!res); 2873 } 2874 2875 private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 2876 if (left.size() != right.size()) 2877 return makeBoolean(true); 2878 2879 boolean res = true; 2880 for (int i = 0; i < left.size(); i++) { 2881 boolean found = false; 2882 for (int j = 0; j < right.size(); j++) { 2883 if (doEquivalent(left.get(i), right.get(j))) { 2884 found = true; 2885 break; 2886 } 2887 } 2888 if (!found) { 2889 res = false; 2890 break; 2891 } 2892 } 2893 return makeBoolean(!res); 2894 } 2895 2896 private List<Base> opOr(List<Base> left, List<Base> right) { 2897 if (left.isEmpty() && right.isEmpty()) 2898 return new ArrayList<Base>(); 2899 else if (convertToBoolean(left) || convertToBoolean(right)) 2900 return makeBoolean(true); 2901 else if (left.isEmpty() || right.isEmpty()) 2902 return new ArrayList<Base>(); 2903 else 2904 return makeBoolean(false); 2905 } 2906 2907 private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException { 2908 if (left.size() == 0) 2909 throw new PathEngineException("Error performing +: left operand has no value"); 2910 if (left.size() > 1) 2911 throw new PathEngineException("Error performing +: left operand has more than one value"); 2912 if (!left.get(0).isPrimitive()) 2913 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 2914 if (right.size() == 0) 2915 throw new PathEngineException("Error performing +: right operand has no value"); 2916 if (right.size() > 1) 2917 throw new PathEngineException("Error performing +: right operand has more than one value"); 2918 if (!right.get(0).isPrimitive()) 2919 throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType())); 2920 2921 List<Base> result = new ArrayList<Base>(); 2922 Base l = left.get(0); 2923 Base r = right.get(0); 2924 if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri")) 2925 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 2926 else if (l.hasType("integer") && r.hasType("integer")) 2927 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 2928 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 2929 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 2930 else 2931 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())); 2932 return result; 2933 } 2934 2935 private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException { 2936 if (left.size() == 0) 2937 throw new PathEngineException("Error performing *: left operand has no value"); 2938 if (left.size() > 1) 2939 throw new PathEngineException("Error performing *: left operand has more than one value"); 2940 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2941 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 2942 if (right.size() == 0) 2943 throw new PathEngineException("Error performing *: right operand has no value"); 2944 if (right.size() > 1) 2945 throw new PathEngineException("Error performing *: right operand has more than one value"); 2946 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2947 throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType())); 2948 2949 List<Base> result = new ArrayList<Base>(); 2950 Base l = left.get(0); 2951 Base r = right.get(0); 2952 2953 if (l.hasType("integer") && r.hasType("integer")) 2954 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 2955 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 2956 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 2957 else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2958 Pair pl = qtyToPair((Quantity) l); 2959 Pair pr = qtyToPair((Quantity) r); 2960 Pair p; 2961 try { 2962 p = worker.getUcumService().multiply(pl, pr); 2963 result.add(pairToQty(p)); 2964 } catch (UcumException e) { 2965 throw new PathEngineException(e.getMessage(), e); 2966 } 2967 } else 2968 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())); 2969 return result; 2970 } 2971 2972 private List<Base> opUnion(List<Base> left, List<Base> right) { 2973 List<Base> result = new ArrayList<Base>(); 2974 for (Base item : left) { 2975 if (!doContains(result, item)) 2976 result.add(item); 2977 } 2978 for (Base item : right) { 2979 if (!doContains(result, item)) 2980 result.add(item); 2981 } 2982 return result; 2983 } 2984 2985 private List<Base> opXor(List<Base> left, List<Base> right) { 2986 if (left.isEmpty() || right.isEmpty()) 2987 return new ArrayList<Base>(); 2988 else 2989 return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right)); 2990 } 2991 2992 private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException { 2993 switch (operation) { 2994 case Equals: 2995 return opEquals(left, right); 2996 case Equivalent: 2997 return opEquivalent(left, right); 2998 case NotEquals: 2999 return opNotEquals(left, right); 3000 case NotEquivalent: 3001 return opNotEquivalent(left, right); 3002 case LessThen: 3003 return opLessThen(left, right); 3004 case Greater: 3005 return opGreater(left, right); 3006 case LessOrEqual: 3007 return opLessOrEqual(left, right); 3008 case GreaterOrEqual: 3009 return opGreaterOrEqual(left, right); 3010 case Union: 3011 return opUnion(left, right); 3012 case In: 3013 return opIn(left, right); 3014 case MemberOf: 3015 return opMemberOf(left, right); 3016 case Contains: 3017 return opContains(left, right); 3018 case Or: 3019 return opOr(left, right); 3020 case And: 3021 return opAnd(left, right); 3022 case Xor: 3023 return opXor(left, right); 3024 case Implies: 3025 return opImplies(left, right); 3026 case Plus: 3027 return opPlus(left, right); 3028 case Times: 3029 return opTimes(left, right); 3030 case Minus: 3031 return opMinus(left, right); 3032 case Concatenate: 3033 return opConcatenate(left, right); 3034 case DivideBy: 3035 return opDivideBy(left, right); 3036 case Div: 3037 return opDiv(left, right); 3038 case Mod: 3039 return opMod(left, right); 3040 case Is: 3041 return opIs(left, right); 3042 case As: 3043 return opAs(left, right); 3044 default: 3045 throw new Error("Not Done Yet: " + operation.toCode()); 3046 } 3047 } 3048 3049 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) { 3050 switch (operation) { 3051 case Equals: 3052 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3053 case Equivalent: 3054 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3055 case NotEquals: 3056 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3057 case NotEquivalent: 3058 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3059 case LessThen: 3060 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3061 case Greater: 3062 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3063 case LessOrEqual: 3064 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3065 case GreaterOrEqual: 3066 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3067 case Is: 3068 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3069 case As: 3070 return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 3071 case Union: 3072 return left.union(right); 3073 case Or: 3074 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3075 case And: 3076 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3077 case Xor: 3078 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3079 case Implies: 3080 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3081 case Times: 3082 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 3083 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 3084 result.addType(TypeDetails.FP_Integer); 3085 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 3086 result.addType(TypeDetails.FP_Decimal); 3087 return result; 3088 case DivideBy: 3089 result = new TypeDetails(CollectionStatus.SINGLETON); 3090 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 3091 result.addType(TypeDetails.FP_Decimal); 3092 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 3093 result.addType(TypeDetails.FP_Decimal); 3094 return result; 3095 case Concatenate: 3096 result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3097 return result; 3098 case Plus: 3099 result = new TypeDetails(CollectionStatus.SINGLETON); 3100 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 3101 result.addType(TypeDetails.FP_Integer); 3102 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 3103 result.addType(TypeDetails.FP_Decimal); 3104 else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) 3105 result.addType(TypeDetails.FP_String); 3106 return result; 3107 case Minus: 3108 result = new TypeDetails(CollectionStatus.SINGLETON); 3109 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 3110 result.addType(TypeDetails.FP_Integer); 3111 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 3112 result.addType(TypeDetails.FP_Decimal); 3113 return result; 3114 case Div: 3115 case Mod: 3116 result = new TypeDetails(CollectionStatus.SINGLETON); 3117 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 3118 result.addType(TypeDetails.FP_Integer); 3119 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 3120 result.addType(TypeDetails.FP_Decimal); 3121 return result; 3122 case In: 3123 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3124 case MemberOf: 3125 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3126 case Contains: 3127 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3128 default: 3129 return null; 3130 } 3131 } 3132 3133 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 3134 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 3135 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 3136 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 3137 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThen, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 3138 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); 3139 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 3140 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 3141 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 3142 // last: implies 3143 return node; 3144 } 3145 3146 private Base pairToQty(Pair p) { 3147 return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions(); 3148 } 3149 3150 /** 3151 * Parse a path for later use using execute 3152 * 3153 * @param path 3154 * @return 3155 * @throws PathEngineException 3156 * @throws Exception 3157 */ 3158 public ExpressionNode parse(String path) throws FHIRLexerException { 3159 FHIRLexer lexer = new FHIRLexer(path); 3160 if (lexer.done()) 3161 throw lexer.error("Path cannot be empty"); 3162 ExpressionNode result = parseExpression(lexer, true); 3163 if (!lexer.done()) 3164 throw lexer.error("Premature ExpressionNode termination at unexpected token \"" + lexer.getCurrent() + "\""); 3165 result.check(); 3166 return result; 3167 } 3168 3169 /** 3170 * Parse a path that is part of some other syntax 3171 * 3172 * @return 3173 * @throws PathEngineException 3174 * @throws Exception 3175 */ 3176 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 3177 ExpressionNode result = parseExpression(lexer, true); 3178 result.check(); 3179 return result; 3180 } 3181 3182 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 3183 ExpressionNode result = new ExpressionNode(lexer.nextId()); 3184 SourceLocation c = lexer.getCurrentStartLocation(); 3185 result.setStart(lexer.getCurrentLocation()); 3186 // special: 3187 if (lexer.getCurrent().equals("-")) { 3188 lexer.take(); 3189 lexer.setCurrent("-" + lexer.getCurrent()); 3190 } 3191 if (lexer.getCurrent().equals("+")) { 3192 lexer.take(); 3193 lexer.setCurrent("+" + lexer.getCurrent()); 3194 } 3195 if (lexer.isConstant(false)) { 3196 boolean isString = lexer.isStringConstant(); 3197 result.setConstant(processConstant(lexer)); 3198 result.setKind(Kind.Constant); 3199 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"))) { 3200 // it's a quantity 3201 String ucum = null; 3202 if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { 3203 String s = lexer.take(); 3204 if (s.equals("year") || s.equals("years")) 3205 ucum = "a"; 3206 else if (s.equals("month") || s.equals("months")) 3207 ucum = "mo"; 3208 else if (s.equals("week") || s.equals("weeks")) 3209 ucum = "wk"; 3210 else if (s.equals("day") || s.equals("days")) 3211 ucum = "d"; 3212 else if (s.equals("hour") || s.equals("hours")) 3213 ucum = "h"; 3214 else if (s.equals("minute") || s.equals("minutes")) 3215 ucum = "min"; 3216 else if (s.equals("second") || s.equals("seconds")) 3217 ucum = "s"; 3218 else // (s.equals("millisecond") || s.equals("milliseconds")) 3219 ucum = "ms"; 3220 } else 3221 ucum = lexer.readConstant("units"); 3222 result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum)); 3223 } 3224 result.setEnd(lexer.getCurrentLocation()); 3225 } else if ("(".equals(lexer.getCurrent())) { 3226 lexer.next(); 3227 result.setKind(Kind.Group); 3228 result.setGroup(parseExpression(lexer, true)); 3229 if (!")".equals(lexer.getCurrent())) 3230 throw lexer.error("Found " + lexer.getCurrent() + " expecting a \")\""); 3231 result.setEnd(lexer.getCurrentLocation()); 3232 lexer.next(); 3233 } else { 3234 if (!lexer.isToken() && !lexer.getCurrent().startsWith("\"")) 3235 throw lexer.error("Found " + lexer.getCurrent() + " expecting a token name"); 3236 if (lexer.getCurrent().startsWith("\"")) 3237 result.setName(lexer.readConstant("Path Name")); 3238 else 3239 result.setName(lexer.take()); 3240 result.setEnd(lexer.getCurrentLocation()); 3241 if (!result.checkName()) 3242 throw lexer.error("Found " + result.getName() + " expecting a valid token name"); 3243 if ("(".equals(lexer.getCurrent())) { 3244 Function f = Function.fromCode(result.getName()); 3245 FunctionDetails details = null; 3246 if (f == null) { 3247 if (hostServices != null) 3248 details = hostServices.resolveFunction(result.getName()); 3249 if (details == null) 3250 throw lexer.error("The name " + result.getName() + " is not a valid function name"); 3251 f = Function.Custom; 3252 } 3253 result.setKind(Kind.Function); 3254 result.setFunction(f); 3255 lexer.next(); 3256 while (!")".equals(lexer.getCurrent())) { 3257 result.getParameters().add(parseExpression(lexer, true)); 3258 if (",".equals(lexer.getCurrent())) 3259 lexer.next(); 3260 else if (!")".equals(lexer.getCurrent())) 3261 throw lexer.error("The token " + lexer.getCurrent() + " is not expected here - either a \",\" or a \")\" expected"); 3262 } 3263 result.setEnd(lexer.getCurrentLocation()); 3264 lexer.next(); 3265 checkParameters(lexer, c, result, details); 3266 } else 3267 result.setKind(Kind.Name); 3268 } 3269 ExpressionNode focus = result; 3270 if ("[".equals(lexer.getCurrent())) { 3271 lexer.next(); 3272 ExpressionNode item = new ExpressionNode(lexer.nextId()); 3273 item.setKind(Kind.Function); 3274 item.setFunction(ExpressionNode.Function.Item); 3275 item.getParameters().add(parseExpression(lexer, true)); 3276 if (!lexer.getCurrent().equals("]")) 3277 throw lexer.error("The token " + lexer.getCurrent() + " is not expected here - a \"]\" expected"); 3278 lexer.next(); 3279 result.setInner(item); 3280 focus = item; 3281 } 3282 if (".".equals(lexer.getCurrent())) { 3283 lexer.next(); 3284 focus.setInner(parseExpression(lexer, false)); 3285 } 3286 result.setProximal(proximal); 3287 if (proximal) { 3288 while (lexer.isOp()) { 3289 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 3290 focus.setOpStart(lexer.getCurrentStartLocation()); 3291 focus.setOpEnd(lexer.getCurrentLocation()); 3292 lexer.next(); 3293 focus.setOpNext(parseExpression(lexer, false)); 3294 focus = focus.getOpNext(); 3295 } 3296 result = organisePrecedence(lexer, result); 3297 } 3298 return result; 3299 } 3300 3301 public Quantity parseQuantityString(String s) { 3302 if (s == null) 3303 return null; 3304 s = s.trim(); 3305 if (s.contains(" ")) { 3306 String v = s.substring(0, s.indexOf(" ")).trim(); 3307 s = s.substring(s.indexOf(" ")).trim(); 3308 if (!Utilities.isDecimal(v)) 3309 return null; 3310 if (s.startsWith("'") && s.endsWith("'")) 3311 return Quantity.fromUcum(v, s.substring(1, s.length() - 1)); 3312 if (s.equals("year") || s.equals("years")) 3313 return Quantity.fromUcum(v, "a"); 3314 else if (s.equals("month") || s.equals("months")) 3315 return Quantity.fromUcum(v, "mo"); 3316 else if (s.equals("week") || s.equals("weeks")) 3317 return Quantity.fromUcum(v, "wk"); 3318 else if (s.equals("day") || s.equals("days")) 3319 return Quantity.fromUcum(v, "d"); 3320 else if (s.equals("hour") || s.equals("hours")) 3321 return Quantity.fromUcum(v, "h"); 3322 else if (s.equals("minute") || s.equals("minutes")) 3323 return Quantity.fromUcum(v, "min"); 3324 else if (s.equals("second") || s.equals("seconds")) 3325 return Quantity.fromUcum(v, "s"); 3326 else if (s.equals("millisecond") || s.equals("milliseconds")) 3327 return Quantity.fromUcum(v, "ms"); 3328 else 3329 return null; 3330 } else { 3331 if (Utilities.isDecimal(s)) 3332 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 3333 else 3334 return null; 3335 } 3336 } 3337 3338 private List<Base> preOperate(List<Base> left, Operation operation) { 3339 switch (operation) { 3340 case And: 3341 return isBoolean(left, false) ? makeBoolean(false) : null; 3342 case Or: 3343 return isBoolean(left, true) ? makeBoolean(true) : null; 3344 case Implies: 3345 return convertToBoolean(left) ? null : makeBoolean(true); 3346 default: 3347 return null; 3348 } 3349 } 3350 3351 private Base processConstant(FHIRLexer lexer) throws FHIRLexerException { 3352 if (lexer.isStringConstant()) { 3353 return new StringType(processConstantString(lexer.take(), lexer)).noExtensions(); 3354 } else if (Utilities.isInteger(lexer.getCurrent())) { 3355 return new IntegerType(lexer.take()).noExtensions(); 3356 } else if (Utilities.isDecimal(lexer.getCurrent())) { 3357 return new DecimalType(lexer.take()).noExtensions(); 3358 } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) { 3359 return new BooleanType(lexer.take()).noExtensions(); 3360 } else if (lexer.getCurrent().equals("{}")) { 3361 lexer.take(); 3362 return null; 3363 } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { 3364 return new FHIRConstant(lexer.take()); 3365 } else 3366 throw lexer.error("Invalid Constant " + lexer.getCurrent()); 3367 } 3368 3369 private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException { 3370 StringBuilder b = new StringBuilder(); 3371 int i = 1; 3372 while (i < s.length() - 1) { 3373 char ch = s.charAt(i); 3374 if (ch == '\\') { 3375 i++; 3376 switch (s.charAt(i)) { 3377 case 't': 3378 b.append('\t'); 3379 break; 3380 case 'r': 3381 b.append('\r'); 3382 break; 3383 case 'n': 3384 b.append('\n'); 3385 break; 3386 case 'f': 3387 b.append('\f'); 3388 break; 3389 case '\'': 3390 b.append('\''); 3391 break; 3392 case '"': 3393 b.append('"'); 3394 break; 3395 case '\\': 3396 b.append('\\'); 3397 break; 3398 case '/': 3399 b.append('/'); 3400 break; 3401 case 'u': 3402 i++; 3403 int uc = Integer.parseInt(s.substring(i, i + 4), 16); 3404 b.append((char) uc); 3405 i = i + 3; 3406 break; 3407 default: 3408 throw lexer.error("Unknown character escape \\" + s.charAt(i)); 3409 } 3410 i++; 3411 } else { 3412 b.append(ch); 3413 i++; 3414 } 3415 } 3416 return b.toString(); 3417 } 3418 3419 private Base processDateConstant(Object appInfo, String value) throws PathEngineException { 3420 if (value.startsWith("T")) 3421 return new TimeType(value.substring(1)).noExtensions(); 3422 String v = value; 3423 if (v.length() > 10) { 3424 int i = v.substring(10).indexOf("-"); 3425 if (i == -1) 3426 i = v.substring(10).indexOf("+"); 3427 if (i == -1) 3428 i = v.substring(10).indexOf("Z"); 3429 v = i == -1 ? value : v.substring(0, 10 + i); 3430 } 3431 if (v.length() > 10) 3432 return new DateTimeType(value).noExtensions(); 3433 else 3434 return new DateType(value).noExtensions(); 3435 } 3436 3437 private boolean qtyEqual(Quantity left, Quantity right) { 3438 if (worker.getUcumService() != null) { 3439 DecimalType dl = qtyToCanonical(left); 3440 DecimalType dr = qtyToCanonical(right); 3441 if (dl != null && dr != null) 3442 return doEquals(dl, dr); 3443 } 3444 return left.equals(right); 3445 } 3446 3447 private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { 3448 if (worker.getUcumService() != null) { 3449 DecimalType dl = qtyToCanonical(left); 3450 DecimalType dr = qtyToCanonical(right); 3451 if (dl != null && dr != null) 3452 return doEquivalent(dl, dr); 3453 } 3454 return left.equals(right); 3455 } 3456 3457 private DecimalType qtyToCanonical(Quantity q) { 3458 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 3459 return null; 3460 try { 3461 Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 3462 Pair c = worker.getUcumService().getCanonicalForm(p); 3463 return new DecimalType(c.getValue().asDecimal()); 3464 } catch (UcumException e) { 3465 return null; 3466 } 3467 } 3468 3469 private Pair qtyToPair(Quantity q) { 3470 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 3471 return null; 3472 try { 3473 return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 3474 } catch (UcumException e) { 3475 return null; 3476 } 3477 } 3478 3479 private Base resolveConstant(ExecutionContext context, Base constant) throws PathEngineException { 3480 if (!(constant instanceof FHIRConstant)) 3481 return constant; 3482 FHIRConstant c = (FHIRConstant) constant; 3483 if (c.getValue().startsWith("%")) { 3484 return resolveConstant(context, c.getValue()); 3485 } else if (c.getValue().startsWith("@")) { 3486 return processDateConstant(context.appInfo, c.getValue().substring(1)); 3487 } else 3488 throw new PathEngineException("Invaild FHIR Constant " + c.getValue()); 3489 } 3490 3491 private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException { 3492 if (s.equals("%sct")) 3493 return new StringType("http://snomed.info/sct").noExtensions(); 3494 else if (s.equals("%loinc")) 3495 return new StringType("http://loinc.org").noExtensions(); 3496 else if (s.equals("%ucum")) 3497 return new StringType("http://unitsofmeasure.org").noExtensions(); 3498 else if (s.equals("%resource")) { 3499 if (context.resource == null) 3500 throw new PathEngineException("Cannot use %resource in this context"); 3501 return context.resource; 3502 } else if (s.equals("%context")) { 3503 return context.context; 3504 } else if (s.equals("%us-zip")) 3505 return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions(); 3506 else if (s.startsWith("%\"vs-")) 3507 return new StringType("http://hl7.org/fhir/ValueSet/" + s.substring(5, s.length() - 1) + "").noExtensions(); 3508 else if (s.startsWith("%\"cs-")) 3509 return new StringType("http://hl7.org/fhir/" + s.substring(5, s.length() - 1) + "").noExtensions(); 3510 else if (s.startsWith("%\"ext-")) 3511 return new StringType("http://hl7.org/fhir/StructureDefinition/" + s.substring(6, s.length() - 1)).noExtensions(); 3512 else if (hostServices == null) 3513 throw new PathEngineException("Unknown fixed constant '" + s + "'"); 3514 else 3515 return hostServices.resolveConstant(context.appInfo, s.substring(1)); 3516 } 3517 3518 private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant) throws PathEngineException { 3519 if (constant instanceof BooleanType) 3520 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3521 else if (constant instanceof IntegerType) 3522 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3523 else if (constant instanceof DecimalType) 3524 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3525 else if (constant instanceof Quantity) 3526 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 3527 else if (constant instanceof FHIRConstant) 3528 return resolveConstantType(context, ((FHIRConstant) constant).getValue()); 3529 else 3530 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3531 } 3532 3533 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException { 3534 if (s.startsWith("@")) { 3535 if (s.startsWith("@T")) 3536 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 3537 else 3538 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3539 } else if (s.equals("%sct")) 3540 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3541 else if (s.equals("%loinc")) 3542 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3543 else if (s.equals("%ucum")) 3544 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3545 else if (s.equals("%resource")) { 3546 if (context.resource == null) 3547 throw new PathEngineException("%resource cannot be used in this context"); 3548 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 3549 } else if (s.equals("%context")) { 3550 return new TypeDetails(CollectionStatus.SINGLETON, context.context); 3551 } else if (s.equals("%map-codes")) 3552 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3553 else if (s.equals("%us-zip")) 3554 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3555 else if (s.startsWith("%\"vs-")) 3556 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3557 else if (s.startsWith("%\"cs-")) 3558 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3559 else if (s.startsWith("%\"ext-")) 3560 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3561 else if (hostServices == null) 3562 throw new PathEngineException("Unknown fixed constant type for '" + s + "'"); 3563 else 3564 return hostServices.resolveConstantType(context.appInfo, s); 3565 } 3566 3567 private String tailDot(String path) { 3568 return path.substring(path.lastIndexOf(".") + 1); 3569 } 3570 3571 private boolean tailMatches(ElementDefinition t, String d) { 3572 String tail = tailDot(t.getPath()); 3573 if (d.contains("[")) 3574 return tail.startsWith(d.substring(0, d.indexOf('['))); 3575 else if (tail.equals(d)) 3576 return true; 3577 else if (t.getType().size() == 1 && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) 3578 return tail.startsWith(d); 3579 3580 return false; 3581 } 3582 3583 public String takeLog() { 3584 if (!hasLog()) 3585 return ""; 3586 String s = log.toString(); 3587 log = new StringBuilder(); 3588 return s; 3589 } 3590 3591 3592 // if the fhir path expressions are allowed to use constants beyond those defined in the specification 3593 // the application can implement them by providing a constant resolver 3594 public interface IEvaluationContext { 3595 /** 3596 * Check the function parameters, and throw an error if they are incorrect, or return the type for the function 3597 * 3598 * @param functionName 3599 * @param parameters 3600 * @return 3601 */ 3602 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException; 3603 3604 /** 3605 * @param appContext 3606 * @param functionName 3607 * @param parameters 3608 * @return 3609 */ 3610 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters); 3611 3612 /** 3613 * when the .log() function is called 3614 * 3615 * @param argument 3616 * @param focus 3617 * @return 3618 */ 3619 public boolean log(String argument, List<Base> focus); 3620 3621 /** 3622 * A constant reference - e.g. a reference to a name that must be resolved in context. 3623 * The % will be removed from the constant name before this is invoked. 3624 * <p> 3625 * This will also be called if the host invokes the FluentPath engine with a context of null 3626 * 3627 * @param appContext - content passed into the fluent path engine 3628 * @param name - name reference to resolve 3629 * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired) 3630 */ 3631 public Base resolveConstant(Object appContext, String name) throws PathEngineException; 3632 3633 // extensibility for functions 3634 3635 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException; 3636 3637 /** 3638 * @param functionName 3639 * @return null if the function is not known 3640 */ 3641 public FunctionDetails resolveFunction(String functionName); 3642 3643 /** 3644 * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null 3645 * 3646 * @param url 3647 * @return 3648 * @throws FHIRException 3649 */ 3650 public Base resolveReference(Object appContext, String url) throws FHIRException; 3651 3652 public class FunctionDetails { 3653 private String description; 3654 private int minParameters; 3655 private int maxParameters; 3656 3657 public FunctionDetails(String description, int minParameters, int maxParameters) { 3658 super(); 3659 this.description = description; 3660 this.minParameters = minParameters; 3661 this.maxParameters = maxParameters; 3662 } 3663 3664 public String getDescription() { 3665 return description; 3666 } 3667 3668 public int getMaxParameters() { 3669 return maxParameters; 3670 } 3671 3672 public int getMinParameters() { 3673 return minParameters; 3674 } 3675 3676 } 3677 3678 } 3679 3680 private class FHIRConstant extends Base { 3681 3682 private static final long serialVersionUID = -8933773658248269439L; 3683 private String value; 3684 3685 public FHIRConstant(String value) { 3686 this.value = value; 3687 } 3688 3689 @Override 3690 public String fhirType() { 3691 return "%constant"; 3692 } 3693 3694 @Override 3695 public String getIdBase() { 3696 return null; 3697 } 3698 3699 @Override 3700 public void setIdBase(String value) { 3701 } 3702 3703 public String getValue() { 3704 return value; 3705 } 3706 3707 @Override 3708 protected void listChildren(List<Property> result) { 3709 } 3710 } 3711 3712 private class ClassTypeInfo extends Base { 3713 private static final long serialVersionUID = 4909223114071029317L; 3714 private Base instance; 3715 3716 public ClassTypeInfo(Base instance) { 3717 super(); 3718 this.instance = instance; 3719 } 3720 3721 @Override 3722 public String fhirType() { 3723 return "ClassInfo"; 3724 } 3725 3726 @Override 3727 public String getIdBase() { 3728 return null; 3729 } 3730 3731 @Override 3732 public void setIdBase(String value) { 3733 } 3734 3735 private String getName() { 3736 if ((instance instanceof Resource)) 3737 return instance.fhirType(); 3738 else if (!(instance instanceof Element) || ((Element) instance).isDisallowExtensions()) 3739 return Utilities.capitalize(instance.fhirType()); 3740 else 3741 return instance.fhirType(); 3742 } 3743 3744 private String getNamespace() { 3745 if ((instance instanceof Resource)) 3746 return "FHIR"; 3747 else if (!(instance instanceof Element) || ((Element) instance).isDisallowExtensions()) 3748 return "System"; 3749 else 3750 return "FHIR"; 3751 } 3752 3753 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 3754 if (name.equals("name")) 3755 return new Base[] {new StringType(getName())}; 3756 else if (name.equals("namespace")) 3757 return new Base[] {new StringType(getNamespace())}; 3758 else 3759 return super.getProperty(hash, name, checkValid); 3760 } 3761 3762 @Override 3763 protected void listChildren(List<Property> result) { 3764 } 3765 } 3766 3767 private class ExecutionContext { 3768 private Object appInfo; 3769 private Base resource; 3770 private Base context; 3771 private Base thisItem; 3772 private List<Base> total; 3773 private Map<String, Base> aliases; 3774 3775 public ExecutionContext(Object appInfo, Base resource, Base context, Map<String, Base> aliases, Base thisItem) { 3776 this.appInfo = appInfo; 3777 this.context = context; 3778 this.resource = resource; 3779 this.aliases = aliases; 3780 this.thisItem = thisItem; 3781 } 3782 3783 public void addAlias(String name, List<Base> focus) throws FHIRException { 3784 if (aliases == null) 3785 aliases = new HashMap<String, Base>(); 3786 else 3787 aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 3788 if (focus.size() > 1) 3789 throw new FHIRException("Attempt to alias a collection, not a singleton"); 3790 aliases.put(name, focus.size() == 0 ? null : focus.get(0)); 3791 } 3792 3793 public Base getAlias(String name) { 3794 return aliases == null ? null : aliases.get(name); 3795 } 3796 3797 public Base getResource() { 3798 return resource; 3799 } 3800 3801 public Base getThisItem() { 3802 return thisItem; 3803 } 3804 3805 public List<Base> getTotal() { 3806 return total; 3807 } 3808 } 3809 3810 private class ExecutionTypeContext { 3811 private Object appInfo; 3812 private String resource; 3813 private String context; 3814 private TypeDetails thisItem; 3815 private TypeDetails total; 3816 3817 3818 public ExecutionTypeContext(Object appInfo, String resource, String context, TypeDetails thisItem) { 3819 super(); 3820 this.appInfo = appInfo; 3821 this.resource = resource; 3822 this.context = context; 3823 this.thisItem = thisItem; 3824 3825 } 3826 3827 public String getResource() { 3828 return resource; 3829 } 3830 3831 public TypeDetails getThisItem() { 3832 return thisItem; 3833 } 3834 3835 3836 } 3837 3838 public class ElementDefinitionMatch { 3839 private ElementDefinition definition; 3840 private String fixedType; 3841 3842 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 3843 super(); 3844 this.definition = definition; 3845 this.fixedType = fixedType; 3846 } 3847 3848 public ElementDefinition getDefinition() { 3849 return definition; 3850 } 3851 3852 public String getFixedType() { 3853 return fixedType; 3854 } 3855 3856 } 3857 3858}