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