001package org.hl7.fhir.instance.utils; 002 003import java.math.BigDecimal; 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.EnumSet; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012 013import org.hl7.fhir.instance.model.Base; 014import org.hl7.fhir.instance.model.BooleanType; 015import org.hl7.fhir.instance.model.DateTimeType; 016import org.hl7.fhir.instance.model.DateType; 017import org.hl7.fhir.instance.model.DecimalType; 018import org.hl7.fhir.instance.model.ElementDefinition; 019import org.hl7.fhir.instance.model.ElementDefinition.TypeRefComponent; 020import org.hl7.fhir.instance.model.ExpressionNode; 021import org.hl7.fhir.instance.model.ExpressionNode.CollectionStatus; 022import org.hl7.fhir.instance.model.ExpressionNode.Function; 023import org.hl7.fhir.instance.model.ExpressionNode.Kind; 024import org.hl7.fhir.instance.model.ExpressionNode.Operation; 025import org.hl7.fhir.instance.model.ExpressionNode.SourceLocation; 026import org.hl7.fhir.instance.model.ExpressionNode.TypeDetails; 027import org.hl7.fhir.instance.model.IntegerType; 028import org.hl7.fhir.instance.model.Resource; 029import org.hl7.fhir.instance.model.StringType; 030import org.hl7.fhir.instance.model.StructureDefinition; 031import org.hl7.fhir.instance.model.TemporalPrecisionEnum; 032import org.hl7.fhir.instance.model.TimeType; 033import org.hl7.fhir.instance.model.Type; 034import org.hl7.fhir.instance.utils.FHIRLexer.FHIRLexerException; 035import org.hl7.fhir.instance.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; 036import org.hl7.fhir.exceptions.DefinitionException; 037import org.hl7.fhir.exceptions.PathEngineException; 038import org.hl7.fhir.exceptions.UcumException; 039import org.hl7.fhir.utilities.Utilities; 040import org.hl7.fhir.utilities.ucum.Decimal; 041 042 043/** 044 * 045 * @author Grahame Grieve 046 * 047 */ 048public class FHIRPathEngine { 049 private IWorkerContext worker; 050 private IEvaluationContext hostServices; 051 private StringBuilder log = new StringBuilder(); 052 private Set<String> primitiveTypes = new HashSet<String>(); 053 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 054 055 // if the fhir path expressions are allowed to use constants beyond those defined in the specification 056 // the application can implement them by providing a constant resolver 057 public interface IEvaluationContext { 058 public class FunctionDetails { 059 private String description; 060 private int minParameters; 061 private int maxParameters; 062 public FunctionDetails(String description, int minParameters, int maxParameters) { 063 super(); 064 this.description = description; 065 this.minParameters = minParameters; 066 this.maxParameters = maxParameters; 067 } 068 public String getDescription() { 069 return description; 070 } 071 public int getMinParameters() { 072 return minParameters; 073 } 074 public int getMaxParameters() { 075 return maxParameters; 076 } 077 078 } 079 080 public Type resolveConstant(Object appContext, String name); 081 public String resolveConstantType(Object appContext, String name); 082 public boolean Log(String argument, List<Base> focus); 083 084 // extensibility for functions 085 /** 086 * 087 * @param functionName 088 * @return null if the function is not known 089 */ 090 public FunctionDetails resolveFunction(String functionName); 091 092 /** 093 * Check the function parameters, and throw an error if they are incorrect, or return the type for the function 094 * @param functionName 095 * @param parameters 096 * @return 097 */ 098 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException; 099 100 /** 101 * @param appContext 102 * @param functionName 103 * @param parameters 104 * @return 105 */ 106 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters); 107 } 108 109 110 /** 111 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 112 */ 113 public FHIRPathEngine(IWorkerContext worker) { 114 super(); 115 this.worker = worker; 116 primitiveTypes.add("string"); 117 primitiveTypes.add("code"); 118 primitiveTypes.add("integer"); 119 primitiveTypes.add("boolean"); 120 primitiveTypes.add("decimal"); 121 primitiveTypes.add("date"); 122 primitiveTypes.add("dateTime"); 123// for (StructureDefinition sd : worker.allStructures()) { 124// if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 125// allTypes.put(sd.getName(), sd); 126// if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 127// primitiveTypes.add(sd.getName()); 128// } 129// } 130 } 131 132 133 // --- 3 methods to override in children ------------------------------------------------------- 134 // if you don't override, it falls through to the using the base reference implementation 135 // HAPI overrides to these to support extensing the base model 136 137 public IEvaluationContext getConstantResolver() { 138 return hostServices; 139 } 140 141 142 public void setConstantResolver(IEvaluationContext constantResolver) { 143 this.hostServices = constantResolver; 144 } 145 146 147 /** 148 * Given an item, return all the children that conform to the pattern described in name 149 * 150 * Possible patterns: 151 * - a simple name (which may be the base of a name with [] e.g. value[x]) 152 * - a name with a type replacement e.g. valueCodeableConcept 153 * - * which means all children 154 * - ** which means all descendants 155 * 156 * @param item 157 * @param name 158 * @param result 159 * @ 160 */ 161 protected void getChildrenByName(Base item, String name, List<Base> result) { 162 List<Base> list = item.listChildrenByName(name); 163 if (list != null) 164 for (Base v : list) 165 if (v != null) 166 result.add(v); 167 } 168 169 // --- public API ------------------------------------------------------- 170 /** 171 * Parse a path for later use using execute 172 * 173 * @param path 174 * @return 175 * @throws PathEngineException 176 * @throws Exception 177 */ 178 public ExpressionNode parse(String path) throws FHIRLexerException { 179 path = path.replace("$.", "$this."); 180 FHIRLexer lexer = new FHIRLexer(path); 181 if (lexer.done()) 182 throw lexer.error("Path cannot be empty"); 183 ExpressionNode result = parseExpression(lexer, true); 184 if (!lexer.done()) 185 throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); 186 result.check(); 187 return result; 188 } 189 190 /** 191 * Parse a path that is part of some other syntax 192 * 193 * @param path 194 * @return 195 * @throws PathEngineException 196 * @throws Exception 197 */ 198 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 199 ExpressionNode result = parseExpression(lexer, true); 200 result.check(); 201 return result; 202 } 203 204 /** 205 * check that paths referred to in the ExpressionNode are valid 206 * 207 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 208 * 209 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 210 * 211 * @param context - the logical type against which this path is applied 212 * @param path - the FHIR Path statement to check 213 * @throws DefinitionException 214 * @throws PathEngineException 215 * @if the path is not valid 216 */ 217 public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 218 // if context is a path that refers to a type, do that conversion now 219 TypeDetails types; 220 if (!context.contains(".")) 221 types = new TypeDetails(CollectionStatus.SINGLETON, context); 222 else { 223 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+context.substring(0, context.indexOf('.'))); 224 if (sd == null) 225 throw new PathEngineException("Unknown context "+context); 226 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 227 if (ed == null) 228 throw new PathEngineException("Unknown context element "+context); 229 if (ed.fixedType != null) 230 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 231 else if (ed.getDefinition().getType().isEmpty() || ( isAbstractType(ed.getDefinition().getType()))) 232 types = new TypeDetails(CollectionStatus.SINGLETON, context); 233 else { 234 types = new TypeDetails(CollectionStatus.SINGLETON); 235 for (TypeRefComponent t : ed.getDefinition().getType()) 236 types.addType(t.getCode()); 237 } 238 } 239 240 return executeType(new ExecutionTypeContext(appContext, resourceType, context, types), types, expr, true); 241 } 242 243 public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 244 return check(appContext, resourceType, context, parse(expr)); 245 } 246 247 /** 248 * evaluate a path and return the matching elements 249 * 250 * @param base - the object against which the path is being evaluated 251 * @param ExpressionNode - the parsed ExpressionNode statement to use 252 * @return 253 * @throws PathEngineException 254 * @ 255 * @ 256 */ 257 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws PathEngineException { 258 List<Base> list = new ArrayList<Base>(); 259 if (base != null) 260 list.add(base); 261 log = new StringBuilder(); 262 return execute(new ExecutionContext(null, null, base, base), list, ExpressionNode, true); 263 } 264 265 /** 266 * evaluate a path and return the matching elements 267 * 268 * @param base - the object against which the path is being evaluated 269 * @param path - the FHIR Path statement to use 270 * @return 271 * @throws FHIRLexerException 272 * @throws PathEngineException 273 * @ 274 * @ 275 */ 276 public List<Base> evaluate(Base base, String path) throws FHIRLexerException, PathEngineException { 277 ExpressionNode exp = parse(path); 278 List<Base> list = new ArrayList<Base>(); 279 if (base != null) 280 list.add(base); 281 log = new StringBuilder(); 282 return execute(new ExecutionContext(null, null, base, base), list, exp, true); 283 } 284 285 /** 286 * evaluate a path and return the matching elements 287 * 288 * @param base - the object against which the path is being evaluated 289 * @param ExpressionNode - the parsed ExpressionNode statement to use 290 * @return 291 * @throws PathEngineException 292 * @ 293 * @ 294 */ 295 public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws PathEngineException { 296 List<Base> list = new ArrayList<Base>(); 297 if (base != null) 298 list.add(base); 299 log = new StringBuilder(); 300 return execute(new ExecutionContext(appContext, resource, base, base), list, ExpressionNode, true); 301 } 302 303 /** 304 * evaluate a path and return the matching elements 305 * 306 * @param base - the object against which the path is being evaluated 307 * @param ExpressionNode - the parsed ExpressionNode statement to use 308 * @return 309 * @throws PathEngineException 310 * @ 311 * @ 312 */ 313 public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws PathEngineException { 314 List<Base> list = new ArrayList<Base>(); 315 if (base != null) 316 list.add(base); 317 log = new StringBuilder(); 318 return execute(new ExecutionContext(appContext, resource, base, base), list, ExpressionNode, true); 319 } 320 321 /** 322 * evaluate a path and return the matching elements 323 * 324 * @param base - the object against which the path is being evaluated 325 * @param path - the FHIR Path statement to use 326 * @return 327 * @throws PathEngineException 328 * @throws FHIRLexerException 329 * @ 330 * @ 331 */ 332 public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws PathEngineException, FHIRLexerException { 333 ExpressionNode exp = parse(path); 334 List<Base> list = new ArrayList<Base>(); 335 if (base != null) 336 list.add(base); 337 log = new StringBuilder(); 338 return execute(new ExecutionContext(appContext, resource, base, base), list, exp, true); 339 } 340 341 /** 342 * evaluate a path and return true or false (e.g. for an invariant) 343 * 344 * @param base - the object against which the path is being evaluated 345 * @param path - the FHIR Path statement to use 346 * @return 347 * @throws FHIRLexerException 348 * @throws PathEngineException 349 * @ 350 * @ 351 */ 352 public boolean evaluateToBoolean(Resource resource, Base base, String path) throws PathEngineException, FHIRLexerException { 353 return convertToBoolean(evaluate(null, resource, base, path)); 354 } 355 356 /** 357 * evaluate a path and return true or false (e.g. for an invariant) 358 * 359 * @param base - the object against which the path is being evaluated 360 * @param path - the FHIR Path statement to use 361 * @return 362 * @throws PathEngineException 363 * @ 364 * @ 365 */ 366 public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws PathEngineException { 367 return convertToBoolean(evaluate(null, resource, base, node)); 368 } 369 370 /** 371 * evaluate a path and return true or false (e.g. for an invariant) 372 * 373 * @param base - the object against which the path is being evaluated 374 * @param path - the FHIR Path statement to use 375 * @return 376 * @throws PathEngineException 377 * @ 378 * @ 379 */ 380 public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws PathEngineException { 381 return convertToBoolean(evaluate(null, resource, base, node)); 382 } 383 384 /** 385 * evaluate a path and a string containing the outcome (for display) 386 * 387 * @param base - the object against which the path is being evaluated 388 * @param path - the FHIR Path statement to use 389 * @return 390 * @throws FHIRLexerException 391 * @throws PathEngineException 392 * @ 393 * @ 394 */ 395 public String evaluateToString(Base base, String path) throws FHIRLexerException, PathEngineException { 396 return convertToString(evaluate(base, path)); 397 } 398 399 /** 400 * worker routine for converting a set of objects to a string representation 401 * 402 * @param items - result from @evaluate 403 * @return 404 */ 405 public String convertToString(List<Base> items) { 406 StringBuilder b = new StringBuilder(); 407 boolean first = true; 408 for (Base item : items) { 409 if (first) 410 first = false; 411 else 412 b.append(','); 413 414 b.append(convertToString(item)); 415 } 416 return b.toString(); 417 } 418 419 private String convertToString(Base item) { 420 if (item.isPrimitive()) 421 return item.primitiveValue(); 422 else 423 return item.getClass().getName(); 424 } 425 426 /** 427 * worker routine for converting a set of objects to a boolean representation (for invariants) 428 * 429 * @param items - result from @evaluate 430 * @return 431 */ 432 public boolean convertToBoolean(List<Base> items) { 433 if (items == null) 434 return false; 435 else if (items.size() == 1 && items.get(0) instanceof BooleanType) 436 return ((BooleanType) items.get(0)).getValue(); 437 else 438 return items.size() > 0; 439 } 440 441 442 private void log(String name, List<Base> contents) { 443 if (hostServices == null || !hostServices.Log(name, contents)) { 444 if (log.length() > 0) 445 log.append("; "); 446 log.append(name); 447 log.append(": "); 448 boolean first = true; 449 for (Base b : contents) { 450 if (first) 451 first = false; 452 else 453 log.append(","); 454 log.append(convertToString(b)); 455 } 456 } 457 } 458 459 public String forLog() { 460 if (log.length() > 0) 461 return " ("+log.toString()+")"; 462 else 463 return ""; 464 } 465 466 private class ExecutionContext { 467 private Object appInfo; 468 private Base resource; 469 private Base context; 470 private Base thisItem; 471 public ExecutionContext(Object appInfo, Base resource, Base context, Base thisItem) { 472 this.appInfo = appInfo; 473 this.resource = resource; 474 this.context = context; 475 this.thisItem = thisItem; 476 } 477 public Base getResource() { 478 return resource; 479 } 480 public Base getThisItem() { 481 return thisItem; 482 } 483 } 484 485 private class ExecutionTypeContext { 486 private Object appInfo; 487 private String resource; 488 private String context; 489 private TypeDetails thisItem; 490 491 492 public ExecutionTypeContext(Object appInfo, String resource, String context, TypeDetails thisItem) { 493 super(); 494 this.appInfo = appInfo; 495 this.resource = resource; 496 this.context = context; 497 this.thisItem = thisItem; 498 } 499 public String getResource() { 500 return resource; 501 } 502 public TypeDetails getThisItem() { 503 return thisItem; 504 } 505 } 506 507 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 508 ExpressionNode result = new ExpressionNode(lexer.nextId()); 509 SourceLocation c = lexer.getCurrentStartLocation(); 510 result.setStart(lexer.getCurrentLocation()); 511 // special: 512 if (lexer.getCurrent().equals("-")) { 513 lexer.take(); 514 lexer.setCurrent("-"+lexer.getCurrent()); 515 } 516 if (lexer.getCurrent().equals("+")) { 517 lexer.take(); 518 lexer.setCurrent("+"+lexer.getCurrent()); 519 } 520 if (lexer.isConstant(false)) { 521 checkConstant(lexer.getCurrent(), lexer); 522 result.setConstant(lexer.take()); 523 result.setKind(Kind.Constant); 524 result.setEnd(lexer.getCurrentLocation()); 525 } else if ("(".equals(lexer.getCurrent())) { 526 lexer.next(); 527 result.setKind(Kind.Group); 528 result.setGroup(parseExpression(lexer, true)); 529 if (!")".equals(lexer.getCurrent())) 530 throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); 531 result.setEnd(lexer.getCurrentLocation()); 532 lexer.next(); 533 } else { 534 if (!lexer.isToken() && !lexer.getCurrent().startsWith("\"")) 535 throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); 536 if (lexer.getCurrent().startsWith("\"")) 537 result.setName(lexer.readConstant("Path Name")); 538 else 539 result.setName(lexer.take()); 540 result.setEnd(lexer.getCurrentLocation()); 541 if (!result.checkName()) 542 throw lexer.error("Found "+result.getName()+" expecting a valid token name"); 543 if ("(".equals(lexer.getCurrent())) { 544 Function f = Function.fromCode(result.getName()); 545 FunctionDetails details = null; 546 if (f == null) { 547 details = hostServices != null ? hostServices.resolveFunction(result.getName()) : null; 548 if (details == null) 549 throw lexer.error("The name "+result.getName()+" is not a valid function name"); 550 f = Function.Custom; 551 } 552 result.setKind(Kind.Function); 553 result.setFunction(f); 554 lexer.next(); 555 while (!")".equals(lexer.getCurrent())) { 556 result.getParameters().add(parseExpression(lexer, true)); 557 if (",".equals(lexer.getCurrent())) 558 lexer.next(); 559 else if (!")".equals(lexer.getCurrent())) 560 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); 561 } 562 result.setEnd(lexer.getCurrentLocation()); 563 lexer.next(); 564 checkParameters(lexer, c, result, details); 565 } else 566 result.setKind(Kind.Name); 567 } 568 ExpressionNode focus = result; 569 if ("[".equals(lexer.getCurrent())) { 570 lexer.next(); 571 ExpressionNode item = new ExpressionNode(lexer.nextId()); 572 item.setKind(Kind.Function); 573 item.setFunction(ExpressionNode.Function.Item); 574 item.getParameters().add(parseExpression(lexer, true)); 575 if (!lexer.getCurrent().equals("]")) 576 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); 577 lexer.next(); 578 result.setInner(item); 579 focus = item; 580 } 581 if (".".equals(lexer.getCurrent())) { 582 lexer.next(); 583 focus.setInner(parseExpression(lexer, false)); 584 } 585 result.setProximal(proximal); 586 if (proximal) { 587 while (lexer.isOp()) { 588 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 589 focus.setOpStart(lexer.getCurrentStartLocation()); 590 focus.setOpEnd(lexer.getCurrentLocation()); 591 lexer.next(); 592 focus.setOpNext(parseExpression(lexer, false)); 593 focus = focus.getOpNext(); 594 } 595 result = organisePrecedence(lexer, result); 596 } 597 return result; 598 } 599 600 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 601 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 602 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 603 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 604 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThen, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 605 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); 606 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 607 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 608 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 609 // last: implies 610 return node; 611 } 612 613 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 614 // work : boolean; 615 // focus, node, group : ExpressionNode; 616 617 assert(start.isProximal()); 618 619 // is there anything to do? 620 boolean work = false; 621 ExpressionNode focus = start.getOpNext(); 622 if (ops.contains(start.getOperation())) { 623 while (focus != null && focus.getOperation() != null) { 624 work = work || !ops.contains(focus.getOperation()); 625 focus = focus.getOpNext(); 626 } 627 } else { 628 while (focus != null && focus.getOperation() != null) { 629 work = work || ops.contains(focus.getOperation()); 630 focus = focus.getOpNext(); 631 } 632 } 633 if (!work) 634 return start; 635 636 // entry point: tricky 637 ExpressionNode group; 638 if (ops.contains(start.getOperation())) { 639 group = newGroup(lexer, start); 640 group.setProximal(true); 641 focus = start; 642 start = group; 643 } else { 644 ExpressionNode node = start; 645 646 focus = node.getOpNext(); 647 while (!ops.contains(focus.getOperation())) { 648 node = focus; 649 focus = focus.getOpNext(); 650 } 651 group = newGroup(lexer, focus); 652 node.setOpNext(group); 653 } 654 655 // now, at this point: 656 // group is the group we are adding to, it already has a .group property filled out. 657 // focus points at the group.group 658 do { 659 // run until we find the end of the sequence 660 while (ops.contains(focus.getOperation())) 661 focus = focus.getOpNext(); 662 if (focus.getOperation() != null) { 663 group.setOperation(focus.getOperation()); 664 group.setOpNext(focus.getOpNext()); 665 focus.setOperation(null); 666 focus.setOpNext(null); 667 // now look for another sequence, and start it 668 ExpressionNode node = group; 669 focus = group.getOpNext(); 670 if (focus != null) { 671 while (focus == null && !ops.contains(focus.getOperation())) { 672 node = focus; 673 focus = focus.getOpNext(); 674 } 675 if (focus != null) { // && (focus.Operation in Ops) - must be true 676 group = newGroup(lexer, focus); 677 node.setOpNext(group); 678 } 679 } 680 } 681 } 682 while (focus != null && focus.getOperation() != null); 683 return start; 684 } 685 686 687 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 688 ExpressionNode result = new ExpressionNode(lexer.nextId()); 689 result.setKind(Kind.Group); 690 result.setGroup(next); 691 result.getGroup().setProximal(true); 692 return result; 693 } 694 695 private void checkConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 696 if (s.startsWith("\'") && s.endsWith("\'")) { 697 int i = 1; 698 while (i < s.length()-1) { 699 char ch = s.charAt(i); 700 if (ch == '\\') { 701 switch (ch) { 702 case 't': 703 case 'r': 704 case 'n': 705 case 'f': 706 case '\'': 707 case '\\': 708 case '/': 709 i++; 710 break; 711 case 'u': 712 if (!Utilities.isHex("0x"+s.substring(i, i+4))) 713 throw lexer.error("Improper unicode escape \\u"+s.substring(i, i+4)); 714 break; 715 default: 716 throw lexer.error("Unknown character escape \\"+ch); 717 } 718 } else 719 i++; 720 } 721 } 722 } 723 724 // procedure CheckParamCount(c : integer); 725 // begin 726 // if exp.Parameters.Count <> c then 727 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 728 // end; 729 730 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 731 if (exp.getParameters().size() != count) 732 throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString()); 733 return true; 734 } 735 736 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 737 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) 738 throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString()); 739 return true; 740 } 741 742 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 743 switch (exp.getFunction()) { 744 case Empty: return checkParamCount(lexer, location, exp, 0); 745 case Not: return checkParamCount(lexer, location, exp, 0); 746 case Exists: return checkParamCount(lexer, location, exp, 0); 747 case SubsetOf: return checkParamCount(lexer, location, exp, 1); 748 case SupersetOf: return checkParamCount(lexer, location, exp, 1); 749 case IsDistinct: return checkParamCount(lexer, location, exp, 0); 750 case Distinct: return checkParamCount(lexer, location, exp, 0); 751 case Count: return checkParamCount(lexer, location, exp, 0); 752 case Where: return checkParamCount(lexer, location, exp, 1); 753 case Select: return checkParamCount(lexer, location, exp, 1); 754 case All: return checkParamCount(lexer, location, exp, 0, 1); 755 case Repeat: return checkParamCount(lexer, location, exp, 1); 756 case Item: return checkParamCount(lexer, location, exp, 1); 757 case As: return checkParamCount(lexer, location, exp, 1); 758 case Is: return checkParamCount(lexer, location, exp, 1); 759 case Single: return checkParamCount(lexer, location, exp, 0); 760 case First: return checkParamCount(lexer, location, exp, 0); 761 case Last: return checkParamCount(lexer, location, exp, 0); 762 case Tail: return checkParamCount(lexer, location, exp, 0); 763 case Skip: return checkParamCount(lexer, location, exp, 1); 764 case Take: return checkParamCount(lexer, location, exp, 1); 765 case Iif: return checkParamCount(lexer, location, exp, 2,3); 766 case ToInteger: return checkParamCount(lexer, location, exp, 0); 767 case ToDecimal: return checkParamCount(lexer, location, exp, 0); 768 case ToString: return checkParamCount(lexer, location, exp, 0); 769 case Substring: return checkParamCount(lexer, location, exp, 1, 2); 770 case StartsWith: return checkParamCount(lexer, location, exp, 1); 771 case EndsWith: return checkParamCount(lexer, location, exp, 1); 772 case Matches: return checkParamCount(lexer, location, exp, 1); 773 case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); 774 case Contains: return checkParamCount(lexer, location, exp, 1); 775 case Replace: return checkParamCount(lexer, location, exp, 2); 776 case Length: return checkParamCount(lexer, location, exp, 0); 777 case Children: return checkParamCount(lexer, location, exp, 0); 778 case Descendants: return checkParamCount(lexer, location, exp, 0); 779 case MemberOf: return checkParamCount(lexer, location, exp, 1); 780 case Trace: return checkParamCount(lexer, location, exp, 1); 781 case Today: return checkParamCount(lexer, location, exp, 0); 782 case Now: return checkParamCount(lexer, location, exp, 0); 783 case Resolve: return checkParamCount(lexer, location, exp, 0); 784 case Extension: return checkParamCount(lexer, location, exp, 1); 785 case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 786 } 787 return false; 788 } 789 790 private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws PathEngineException { 791// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 792 List<Base> work = new ArrayList<Base>(); 793 switch (exp.getKind()) { 794 case Name: 795 if (atEntry && exp.getName().equals("$this")) 796 work.add(context.getThisItem()); 797 else 798 for (Base item : focus) { 799 List<Base> outcome = execute(context, item, exp, atEntry); 800 for (Base base : outcome) 801 if (base != null) 802 work.add(base); 803 } 804 break; 805 case Function: 806 List<Base> work2 = evaluateFunction(context, focus, exp); 807 work.addAll(work2); 808 break; 809 case Constant: 810 Base b = processConstant(context, exp.getConstant()); 811 if (b != null) 812 work.add(b); 813 break; 814 case Group: 815 work2 = execute(context, focus, exp.getGroup(), atEntry); 816 work.addAll(work2); 817 } 818 819 if (exp.getInner() != null) 820 work = execute(context, work, exp.getInner(), false); 821 822 if (exp.isProximal() && exp.getOperation() != null) { 823 ExpressionNode next = exp.getOpNext(); 824 ExpressionNode last = exp; 825 while (next != null) { 826 List<Base> work2 = preOperate(work, last.getOperation()); 827 if (work2 != null) 828 work = work2; 829 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 830 work2 = executeTypeName(context, focus, next, false); 831 work = operate(work, last.getOperation(), work2); 832 } else { 833 work2 = execute(context, focus, next, true); 834 work = operate(work, last.getOperation(), work2); 835// System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); 836 } 837 last = next; 838 next = next.getOpNext(); 839 } 840 } 841// System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); 842 return work; 843 } 844 845 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 846 List<Base> result = new ArrayList<Base>(); 847 result.add(new StringType(next.getName())); 848 return result; 849 } 850 851 852 private List<Base> preOperate(List<Base> left, Operation operation) { 853 switch (operation) { 854 case And: 855 return isBoolean(left, false) ? makeBoolean(false) : null; 856 case Or: 857 return isBoolean(left, true) ? makeBoolean(true) : null; 858 case Implies: 859 return convertToBoolean(left) ? null : makeBoolean(true); 860 default: 861 return null; 862 } 863 } 864 865 private List<Base> makeBoolean(boolean b) { 866 List<Base> res = new ArrayList<Base>(); 867 res.add(new BooleanType(b)); 868 return res; 869 } 870 871 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 872 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 873 } 874 875 private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 876// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 877 TypeDetails result = new TypeDetails(null); 878 switch (exp.getKind()) { 879 case Name: 880 if (atEntry && exp.getName().equals("$this")) 881 result.update(context.getThisItem()); 882 else { 883 for (String s : focus.getTypes()) { 884 result.update(executeType(s, exp, atEntry)); 885 } 886 if (result.hasNoTypes()) 887 throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe()); 888 } 889 break; 890 case Function: 891 result.update(evaluateFunctionType(context, focus, exp)); 892 break; 893 case Constant: 894 result.addType(readConstantType(context, exp.getConstant())); 895 break; 896 case Group: 897 result.update(executeType(context, focus, exp.getGroup(), atEntry)); 898 } 899 exp.setTypes(result); 900 901 if (exp.getInner() != null) { 902 result = executeType(context, result, exp.getInner(), false); 903 } 904 905 if (exp.isProximal() && exp.getOperation() != null) { 906 ExpressionNode next = exp.getOpNext(); 907 ExpressionNode last = exp; 908 while (next != null) { 909 TypeDetails work; 910 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) 911 work = executeTypeName(context, focus, next, atEntry); 912 else 913 work = executeType(context, focus, next, atEntry); 914 result = operateTypes(result, last.getOperation(), work); 915 last = next; 916 next = next.getOpNext(); 917 } 918 exp.setOpTypes(result); 919 } 920 return result; 921 } 922 923 private Base processConstant(ExecutionContext context, String constant) throws PathEngineException { 924 if (constant.equals("true")) { 925 return new BooleanType(true); 926 } else if (constant.equals("false")) { 927 return new BooleanType(false); 928 } else if (constant.equals("{}")) { 929 return null; 930 } else if (Utilities.isInteger(constant)) { 931 return new IntegerType(constant); 932 } else if (Utilities.isDecimal(constant)) { 933 return new DecimalType(constant); 934 } else if (constant.startsWith("\'")) { 935 return new StringType(processConstantString(constant)); 936 } else if (constant.startsWith("%")) { 937 return resolveConstant(context, constant); 938 } else if (constant.startsWith("@")) { 939 return processDateConstant(context.appInfo, constant.substring(1)); 940 } else { 941 return new StringType(constant); 942 } 943 } 944 945 private Base processDateConstant(Object appInfo, String value) throws PathEngineException { 946 if (value.startsWith("T")) 947 return new TimeType(value.substring(1)); 948 String v = value; 949 if (v.length() > 10) { 950 int i = v.substring(10).indexOf("-"); 951 if (i == -1) 952 i = v.substring(10).indexOf("+"); 953 if (i == -1) 954 i = v.substring(10).indexOf("Z"); 955 v = i == -1 ? value : v.substring(0, 10+i); 956 } 957 if (v.length() > 10) 958 return new DateTimeType(value); 959 else 960 return new DateType(value); 961 } 962 963 964 private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException { 965 if (s.equals("%sct")) 966 return new StringType("http://snomed.info/sct"); 967 else if (s.equals("%loinc")) 968 return new StringType("http://loinc.org"); 969 else if (s.equals("%ucum")) 970 return new StringType("http://unitsofmeasure.org"); 971 else if (s.equals("%context")) 972 return context.context; 973 else if (s.equals("%resource")) { 974 if (context.resource == null) 975 throw new PathEngineException("Cannot use %resource in this context"); 976 return context.resource; 977 } else if (s.equals("%us-zip")) 978 return new StringType("[0-9]{5}(-[0-9]{4}){0,1}"); 979 else if (s.startsWith("%\"vs-")) 980 return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+""); 981 else if (s.startsWith("%\"cs-")) 982 return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+""); 983 else if (s.startsWith("%\"ext-")) 984 return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)); 985 else if (hostServices == null) 986 throw new PathEngineException("Unknown fixed constant '"+s+"'"); 987 else 988 return hostServices.resolveConstant(context.appInfo, s); 989 } 990 991 992 private String processConstantString(String s) throws PathEngineException { 993 StringBuilder b = new StringBuilder(); 994 int i = 1; 995 while (i < s.length()-1) { 996 char ch = s.charAt(i); 997 if (ch == '\\') { 998 i++; 999 switch (s.charAt(i)) { 1000 case 't': 1001 b.append('\t'); 1002 break; 1003 case 'r': 1004 b.append('\r'); 1005 break; 1006 case 'n': 1007 b.append('\n'); 1008 break; 1009 case 'f': 1010 b.append('\f'); 1011 break; 1012 case '\'': 1013 b.append('\''); 1014 break; 1015 case '\\': 1016 b.append('\\'); 1017 break; 1018 case '/': 1019 b.append('/'); 1020 break; 1021 case 'u': 1022 i++; 1023 int uc = Integer.parseInt(s.substring(i, i+4), 16); 1024 b.append((char) uc); 1025 i = i + 4; 1026 break; 1027 default: 1028 throw new PathEngineException("Unknown character escape \\"+s.charAt(i)); 1029 } 1030 } else { 1031 b.append(ch); 1032 i++; 1033 } 1034 } 1035 return b.toString(); 1036 } 1037 1038 1039 private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws PathEngineException { 1040 switch (operation) { 1041 case Equals: return opEquals(left, right); 1042 case Equivalent: return opEquivalent(left, right); 1043 case NotEquals: return opNotEquals(left, right); 1044 case NotEquivalent: return opNotEquivalent(left, right); 1045 case LessThen: return opLessThen(left, right); 1046 case Greater: return opGreater(left, right); 1047 case LessOrEqual: return opLessOrEqual(left, right); 1048 case GreaterOrEqual: return opGreaterOrEqual(left, right); 1049 case Union: return opUnion(left, right); 1050 case In: return opIn(left, right); 1051 case Contains: return opContains(left, right); 1052 case Or: return opOr(left, right); 1053 case And: return opAnd(left, right); 1054 case Xor: return opXor(left, right); 1055 case Implies: return opImplies(left, right); 1056 case Plus: return opPlus(left, right); 1057 case Times: return opTimes(left, right); 1058 case Minus: return opMinus(left, right); 1059 case Concatenate: return opConcatenate(left, right); 1060 case DivideBy: return opDivideBy(left, right); 1061 case Div: return opDiv(left, right); 1062 case Mod: return opMod(left, right); 1063 case Is: return opIs(left, right); 1064 case As: return opAs(left, right); 1065 default: 1066 throw new Error("Not Done Yet: "+operation.toCode()); 1067 } 1068 } 1069 1070 private List<Base> opAs(List<Base> left, List<Base> right) { 1071 List<Base> result = new ArrayList<Base>(); 1072 if (left.size() != 1 || right.size() != 1) 1073 return result; 1074 else { 1075 String tn = convertToString(right); 1076 if (tn.equals(left.get(0).fhirType())) 1077 result.add(left.get(0)); 1078 } 1079 return result; 1080 } 1081 1082 1083 private List<Base> opIs(List<Base> left, List<Base> right) { 1084 List<Base> result = new ArrayList<Base>(); 1085 if (left.size() != 1 || right.size() != 1) 1086 result.add(new BooleanType(false)); 1087 else { 1088 String tn = convertToString(right); 1089 result.add(new BooleanType(left.get(0).hasType(tn))); 1090 } 1091 return result; 1092 } 1093 1094 1095 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) { 1096 switch (operation) { 1097 case Equals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1098 case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1099 case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1100 case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1101 case LessThen: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1102 case Greater: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1103 case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1104 case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1105 case Is: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1106 case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 1107 case Union: return left.union(right); 1108 case Or: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1109 case And: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1110 case Xor: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1111 case Implies : return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1112 case Times: 1113 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 1114 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1115 result.addType("integer"); 1116 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1117 result.addType("decimal"); 1118 return result; 1119 case DivideBy: 1120 result = new TypeDetails(CollectionStatus.SINGLETON); 1121 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1122 result.addType("decimal"); 1123 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1124 result.addType("decimal"); 1125 return result; 1126 case Concatenate: 1127 result = new TypeDetails(CollectionStatus.SINGLETON, ""); 1128 return result; 1129 case Plus: 1130 result = new TypeDetails(CollectionStatus.SINGLETON); 1131 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1132 result.addType("integer"); 1133 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1134 result.addType("decimal"); 1135 else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) 1136 result.addType("string"); 1137 return result; 1138 case Minus: 1139 result = new TypeDetails(CollectionStatus.SINGLETON); 1140 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1141 result.addType("integer"); 1142 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1143 result.addType("decimal"); 1144 return result; 1145 case Div: 1146 case Mod: 1147 result = new TypeDetails(CollectionStatus.SINGLETON); 1148 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1149 result.addType("integer"); 1150 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1151 result.addType("decimal"); 1152 return result; 1153 case In: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1154 case Contains: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1155 default: 1156 return null; 1157 } 1158 } 1159 1160 1161 private List<Base> opEquals(List<Base> left, List<Base> right) { 1162 if (left.size() != right.size()) 1163 return makeBoolean(false); 1164 1165 boolean res = true; 1166 for (int i = 0; i < left.size(); i++) { 1167 if (!doEquals(left.get(i), right.get(i))) { 1168 res = false; 1169 break; 1170 } 1171 } 1172 return makeBoolean(res); 1173 } 1174 1175 private List<Base> opNotEquals(List<Base> left, List<Base> right) { 1176 if (left.size() != right.size()) 1177 return makeBoolean(true); 1178 1179 boolean res = true; 1180 for (int i = 0; i < left.size(); i++) { 1181 if (!doEquals(left.get(i), right.get(i))) { 1182 res = false; 1183 break; 1184 } 1185 } 1186 return makeBoolean(!res); 1187 } 1188 1189 private boolean doEquals(Base left, Base right) { 1190 if (left.isPrimitive() && right.isPrimitive()) 1191 return Base.equals(left.primitiveValue(), right.primitiveValue()); 1192 else 1193 return Base.compareDeep(left, right, false); 1194 } 1195 1196 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 1197 if (left.hasType("integer") && right.hasType("integer")) 1198 return doEquals(left, right); 1199 if (left.hasType("boolean") && right.hasType("boolean")) 1200 return doEquals(left, right); 1201 if (left.hasType("integer", "decimal") && right.hasType("integer", "decimal")) 1202 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1203 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) 1204 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1205 if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri")) 1206 return Utilities.equivalent(convertToString(left), convertToString(right)); 1207 1208 throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType())); 1209 } 1210 1211 private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1212 if (left.size() != right.size()) 1213 return makeBoolean(false); 1214 1215 boolean res = true; 1216 for (int i = 0; i < left.size(); i++) { 1217 boolean found = false; 1218 for (int j = 0; j < right.size(); j++) { 1219 if (doEquivalent(left.get(i), right.get(j))) { 1220 found = true; 1221 break; 1222 } 1223 } 1224 if (!found) { 1225 res = false; 1226 break; 1227 } 1228 } 1229 return makeBoolean(res); 1230 } 1231 1232 private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1233 if (left.size() != right.size()) 1234 return makeBoolean(true); 1235 1236 boolean res = true; 1237 for (int i = 0; i < left.size(); i++) { 1238 boolean found = false; 1239 for (int j = 0; j < right.size(); j++) { 1240 if (doEquivalent(left.get(i), right.get(j))) { 1241 found = true; 1242 break; 1243 } 1244 } 1245 if (!found) { 1246 res = false; 1247 break; 1248 } 1249 } 1250 return makeBoolean(!res); 1251 } 1252 1253 private List<Base> opLessThen(List<Base> left, List<Base> right) throws PathEngineException { 1254 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1255 Base l = left.get(0); 1256 Base r = right.get(0); 1257 if (l.hasType("string") && r.hasType("string")) 1258 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1259 else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 1260 return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); 1261 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1262 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1263 else if ((l.hasType("time")) && (r.hasType("time"))) 1264 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1265 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1266 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1267 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1268 if (Base.compareDeep(lUnit, rUnit, true)) { 1269 return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1270 } else { 1271 throw new PathEngineException("Canonical Comparison isn't done yet"); 1272 } 1273 } 1274 return new ArrayList<Base>(); 1275 } 1276 1277 private List<Base> opGreater(List<Base> left, List<Base> right) throws PathEngineException { 1278 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1279 Base l = left.get(0); 1280 Base r = right.get(0); 1281 if (l.hasType("string") && r.hasType("string")) 1282 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1283 else if ((l.hasType("integer", "decimal")) && (r.hasType("integer", "decimal"))) 1284 return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); 1285 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1286 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1287 else if ((l.hasType("time")) && (r.hasType("time"))) 1288 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1289 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1290 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1291 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1292 if (Base.compareDeep(lUnit, rUnit, true)) { 1293 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1294 } else { 1295 throw new PathEngineException("Canonical Comparison isn't done yet"); 1296 } 1297 } 1298 return new ArrayList<Base>(); 1299 } 1300 1301 private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws PathEngineException { 1302 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1303 Base l = left.get(0); 1304 Base r = right.get(0); 1305 if (l.hasType("string") && r.hasType("string")) 1306 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1307 else if ((l.hasType("integer", "decimal")) && (r.hasType("integer", "decimal"))) 1308 return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); 1309 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1310 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1311 else if ((l.hasType("time")) && (r.hasType("time"))) 1312 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1313 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1314 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 1315 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 1316 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 1317 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 1318 if ((lunit == null && runit == null) || lunit.equals(runit)) { 1319 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1320 } else { 1321 throw new PathEngineException("Canonical Comparison isn't done yet"); 1322 } 1323 } 1324 return new ArrayList<Base>(); 1325 } 1326 1327 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws PathEngineException { 1328 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1329 Base l = left.get(0); 1330 Base r = right.get(0); 1331 if (l.hasType("string") && r.hasType("string")) 1332 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1333 else if ((l.hasType("integer", "decimal")) && (r.hasType("integer", "decimal"))) 1334 return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); 1335 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1336 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1337 else if ((l.hasType("time")) && (r.hasType("time"))) 1338 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1339 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1340 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1341 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1342 if (Base.compareDeep(lUnit, rUnit, true)) { 1343 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1344 } else { 1345 throw new PathEngineException("Canonical Comparison isn't done yet"); 1346 } 1347 } 1348 return new ArrayList<Base>(); 1349 } 1350 1351 private List<Base> opIn(List<Base> left, List<Base> right) { 1352 boolean ans = true; 1353 for (Base l : left) { 1354 boolean f = false; 1355 for (Base r : right) 1356 if (doEquals(l, r)) { 1357 f = true; 1358 break; 1359 } 1360 if (!f) { 1361 ans = false; 1362 break; 1363 } 1364 } 1365 return makeBoolean(ans); 1366 } 1367 1368 private List<Base> opContains(List<Base> left, List<Base> right) { 1369 boolean ans = true; 1370 for (Base r : right) { 1371 boolean f = false; 1372 for (Base l : left) 1373 if (doEquals(l, r)) { 1374 f = true; 1375 break; 1376 } 1377 if (!f) { 1378 ans = false; 1379 break; 1380 } 1381 } 1382 return makeBoolean(ans); 1383 } 1384 1385 private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException { 1386 if (left.size() == 0) 1387 throw new PathEngineException("Error performing +: left operand has no value"); 1388 if (left.size() > 1) 1389 throw new PathEngineException("Error performing +: left operand has more than one value"); 1390 if (!left.get(0).isPrimitive()) 1391 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1392 if (right.size() == 0) 1393 throw new PathEngineException("Error performing +: right operand has no value"); 1394 if (right.size() > 1) 1395 throw new PathEngineException("Error performing +: right operand has more than one value"); 1396 if (!right.get(0).isPrimitive()) 1397 throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType())); 1398 1399 List<Base> result = new ArrayList<Base>(); 1400 Base l = left.get(0); 1401 Base r = right.get(0); 1402 if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri")) 1403 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 1404 else if (l.hasType("integer") && r.hasType("integer")) 1405 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 1406 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1407 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 1408 else 1409 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())); 1410 return result; 1411 } 1412 1413 private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException { 1414 if (left.size() == 0) 1415 throw new PathEngineException("Error performing *: left operand has no value"); 1416 if (left.size() > 1) 1417 throw new PathEngineException("Error performing *: left operand has more than one value"); 1418 if (!left.get(0).isPrimitive()) 1419 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1420 if (right.size() == 0) 1421 throw new PathEngineException("Error performing *: right operand has no value"); 1422 if (right.size() > 1) 1423 throw new PathEngineException("Error performing *: right operand has more than one value"); 1424 if (!right.get(0).isPrimitive()) 1425 throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType())); 1426 1427 List<Base> result = new ArrayList<Base>(); 1428 Base l = left.get(0); 1429 Base r = right.get(0); 1430 1431 if (l.hasType("integer") && r.hasType("integer")) 1432 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 1433 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1434 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 1435 else 1436 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())); 1437 return result; 1438 } 1439 1440 private List<Base> opConcatenate(List<Base> left, List<Base> right) { 1441 List<Base> result = new ArrayList<Base>(); 1442 result.add(new StringType(convertToString(left) + convertToString((right)))); 1443 return result; 1444 } 1445 1446 private List<Base> opUnion(List<Base> left, List<Base> right) { 1447 List<Base> result = new ArrayList<Base>(); 1448 for (Base item : left) { 1449 if (!doContains(result, item)) 1450 result.add(item); 1451 } 1452 for (Base item : right) { 1453 if (!doContains(result, item)) 1454 result.add(item); 1455 } 1456 return result; 1457 } 1458 1459 private boolean doContains(List<Base> list, Base item) { 1460 for (Base test : list) 1461 if (doEquals(test, item)) 1462 return true; 1463 return false; 1464 } 1465 1466 1467 private List<Base> opAnd(List<Base> left, List<Base> right) { 1468 if (left.isEmpty() && right.isEmpty()) 1469 return new ArrayList<Base>(); 1470 else if (isBoolean(left, false) || isBoolean(right, false)) 1471 return makeBoolean(false); 1472 else if (left.isEmpty() || right.isEmpty()) 1473 return new ArrayList<Base>(); 1474 else if (convertToBoolean(left) && convertToBoolean(right)) 1475 return makeBoolean(true); 1476 else 1477 return makeBoolean(false); 1478 } 1479 1480 private boolean isBoolean(List<Base> list, boolean b) { 1481 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 1482 } 1483 1484 private List<Base> opOr(List<Base> left, List<Base> right) { 1485 if (left.isEmpty() && right.isEmpty()) 1486 return new ArrayList<Base>(); 1487 else if (convertToBoolean(left) || convertToBoolean(right)) 1488 return makeBoolean(true); 1489 else if (left.isEmpty() || right.isEmpty()) 1490 return new ArrayList<Base>(); 1491 else 1492 return makeBoolean(false); 1493 } 1494 1495 private List<Base> opXor(List<Base> left, List<Base> right) { 1496 if (left.isEmpty() && right.isEmpty()) 1497 return makeBoolean(false); 1498 else 1499 return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right)); 1500 } 1501 1502 private List<Base> opImplies(List<Base> left, List<Base> right) { 1503 if (!convertToBoolean(left)) 1504 return makeBoolean(true); 1505 else if (right.size() == 0) 1506 return new ArrayList<Base>(); 1507 else 1508 return makeBoolean(convertToBoolean(right)); 1509 } 1510 1511 1512 private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException { 1513 if (left.size() == 0) 1514 throw new PathEngineException("Error performing -: left operand has no value"); 1515 if (left.size() > 1) 1516 throw new PathEngineException("Error performing -: left operand has more than one value"); 1517 if (!left.get(0).isPrimitive()) 1518 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 1519 if (right.size() == 0) 1520 throw new PathEngineException("Error performing -: right operand has no value"); 1521 if (right.size() > 1) 1522 throw new PathEngineException("Error performing -: right operand has more than one value"); 1523 if (!right.get(0).isPrimitive()) 1524 throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType())); 1525 1526 List<Base> result = new ArrayList<Base>(); 1527 Base l = left.get(0); 1528 Base r = right.get(0); 1529 1530 if (l.hasType("integer") && r.hasType("integer")) 1531 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 1532 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1533 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 1534 else 1535 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())); 1536 return result; 1537 } 1538 1539 private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException { 1540 if (left.size() == 0) 1541 throw new PathEngineException("Error performing /: left operand has no value"); 1542 if (left.size() > 1) 1543 throw new PathEngineException("Error performing /: left operand has more than one value"); 1544 if (!left.get(0).isPrimitive()) 1545 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 1546 if (right.size() == 0) 1547 throw new PathEngineException("Error performing /: right operand has no value"); 1548 if (right.size() > 1) 1549 throw new PathEngineException("Error performing /: right operand has more than one value"); 1550 if (!right.get(0).isPrimitive()) 1551 throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType())); 1552 1553 List<Base> result = new ArrayList<Base>(); 1554 Base l = left.get(0); 1555 Base r = right.get(0); 1556 1557 if (l.hasType("integer", "decimal") && r.hasType("integer", "decimal")) { 1558 Decimal d1; 1559 try { 1560 d1 = new Decimal(l.primitiveValue()); 1561 Decimal d2 = new Decimal(r.primitiveValue()); 1562 result.add(new DecimalType(d1.divide(d2).asDecimal())); 1563 } catch (UcumException e) { 1564 throw new PathEngineException(e); 1565 } 1566 } 1567 else 1568 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())); 1569 return result; 1570 } 1571 1572 private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException { 1573 if (left.size() == 0) 1574 throw new PathEngineException("Error performing div: left operand has no value"); 1575 if (left.size() > 1) 1576 throw new PathEngineException("Error performing div: left operand has more than one value"); 1577 if (!left.get(0).isPrimitive()) 1578 throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType())); 1579 if (right.size() == 0) 1580 throw new PathEngineException("Error performing div: right operand has no value"); 1581 if (right.size() > 1) 1582 throw new PathEngineException("Error performing div: right operand has more than one value"); 1583 if (!right.get(0).isPrimitive()) 1584 throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType())); 1585 1586 List<Base> result = new ArrayList<Base>(); 1587 Base l = left.get(0); 1588 Base r = right.get(0); 1589 1590 if (l.hasType("integer") && r.hasType("integer")) 1591 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue()))); 1592 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 1593 Decimal d1; 1594 try { 1595 d1 = new Decimal(l.primitiveValue()); 1596 Decimal d2 = new Decimal(r.primitiveValue()); 1597 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 1598 } catch (UcumException e) { 1599 throw new PathEngineException(e); 1600 } 1601 } 1602 else 1603 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())); 1604 return result; 1605 } 1606 1607 private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException { 1608 if (left.size() == 0) 1609 throw new PathEngineException("Error performing mod: left operand has no value"); 1610 if (left.size() > 1) 1611 throw new PathEngineException("Error performing mod: left operand has more than one value"); 1612 if (!left.get(0).isPrimitive()) 1613 throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType())); 1614 if (right.size() == 0) 1615 throw new PathEngineException("Error performing mod: right operand has no value"); 1616 if (right.size() > 1) 1617 throw new PathEngineException("Error performing mod: right operand has more than one value"); 1618 if (!right.get(0).isPrimitive()) 1619 throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType())); 1620 1621 List<Base> result = new ArrayList<Base>(); 1622 Base l = left.get(0); 1623 Base r = right.get(0); 1624 1625 if (l.hasType("integer") && r.hasType("integer")) 1626 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue()))); 1627 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 1628 Decimal d1; 1629 try { 1630 d1 = new Decimal(l.primitiveValue()); 1631 Decimal d2 = new Decimal(r.primitiveValue()); 1632 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 1633 } catch (UcumException e) { 1634 throw new PathEngineException(e); 1635 } 1636 } 1637 else 1638 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())); 1639 return result; 1640 } 1641 1642 1643 private String readConstantType(ExecutionTypeContext context, String constant) throws PathEngineException { 1644 if (constant.equals("true")) 1645 return "boolean"; 1646 else if (constant.equals("false")) 1647 return "boolean"; 1648 else if (Utilities.isInteger(constant)) 1649 return "integer"; 1650 else if (Utilities.isDecimal(constant)) 1651 return "decimal"; 1652 else if (constant.startsWith("%")) 1653 return resolveConstantType(context, constant); 1654 else 1655 return "string"; 1656 } 1657 1658 private String resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException { 1659 if (s.equals("%sct")) 1660 return "string"; 1661 else if (s.equals("%loinc")) 1662 return "string"; 1663 else if (s.equals("%ucum")) 1664 return "string"; 1665 else if (s.equals("%context")) 1666 return context.context; 1667 else if (s.equals("%resource")) { 1668 if (context.resource == null) 1669 throw new PathEngineException("%resource cannot be used in this context"); 1670 return context.resource; 1671 } else if (s.equals("%map-codes")) 1672 return "string"; 1673 else if (s.equals("%us-zip")) 1674 return "string"; 1675 else if (s.startsWith("%\"vs-")) 1676 return "string"; 1677 else if (s.startsWith("%\"cs-")) 1678 return "string"; 1679 else if (s.startsWith("%\"ext-")) 1680 return "string"; 1681 else if (hostServices == null) 1682 throw new PathEngineException("Unknown fixed constant type for '"+s+"'"); 1683 else 1684 return hostServices.resolveConstantType(context.appInfo, s); 1685 } 1686 1687 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) { 1688 List<Base> result = new ArrayList<Base>(); 1689 if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 1690 if (item instanceof Resource && ((Resource) item).getResourceType().toString().equals(exp.getName())) 1691 result.add(item); 1692 } else 1693 getChildrenByName(item, exp.getName(), result); 1694 return result; 1695 } 1696 1697 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1698 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && type.equals(exp.getName())) // special case for start up 1699 return new TypeDetails(CollectionStatus.SINGLETON, type); 1700 TypeDetails result = new TypeDetails(null); 1701 getChildTypesByName(type, exp.getName(), result); 1702 return result; 1703 } 1704 1705 1706 @SuppressWarnings("unchecked") 1707 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { 1708 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 1709 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As) 1710 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, "string")); 1711 else 1712 for (ExpressionNode expr : exp.getParameters()) { 1713 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select) 1714 paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); 1715 else if (exp.getFunction() == Function.Repeat) 1716 ; // it turns out you can't really test this 1717 else 1718 paramTypes.add(executeType(context, focus, expr, true)); 1719 } 1720 switch (exp.getFunction()) { 1721 case Empty : 1722 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1723 case Not : 1724 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1725 case Exists : 1726 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1727 case SubsetOf : { 1728 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 1729 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1730 } 1731 case SupersetOf : { 1732 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 1733 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1734 } 1735 case IsDistinct : 1736 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1737 case Distinct : 1738 return focus; 1739 case Count : 1740 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1741 case Where : 1742 return focus; 1743 case Select : 1744 return anything(focus.getCollectionStatus()); 1745 case All : 1746 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1747 case Repeat : 1748 return anything(focus.getCollectionStatus()); 1749 case Item : { 1750 checkOrdered(focus, "item"); 1751 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1752 return focus; 1753 } 1754 case As : { 1755 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1756 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 1757 } 1758 case Is : { 1759 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1760 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1761 } 1762 case Single : 1763 return focus.toSingleton(); 1764 case First : { 1765 checkOrdered(focus, "first"); 1766 return focus.toSingleton(); 1767 } 1768 case Last : { 1769 checkOrdered(focus, "last"); 1770 return focus.toSingleton(); 1771 } 1772 case Tail : { 1773 checkOrdered(focus, "tail"); 1774 return focus; 1775 } 1776 case Skip : { 1777 checkOrdered(focus, "skip"); 1778 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1779 return focus; 1780 } 1781 case Take : { 1782 checkOrdered(focus, "take"); 1783 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1784 return focus; 1785 } 1786 case Iif : { 1787 TypeDetails types = new TypeDetails(null); 1788 types.update(paramTypes.get(0)); 1789 if (paramTypes.size() > 1) 1790 types.update(paramTypes.get(1)); 1791 return types; 1792 } 1793 case ToInteger : { 1794 checkContextPrimitive(focus, "toInteger"); 1795 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1796 } 1797 case ToDecimal : { 1798 checkContextPrimitive(focus, "toDecimal"); 1799 return new TypeDetails(CollectionStatus.SINGLETON, "decimal"); 1800 } 1801 case ToString : { 1802 checkContextPrimitive(focus, "toString"); 1803 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1804 } 1805 case Substring : { 1806 checkContextString(focus, "subString"); 1807 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"), new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1808 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1809 } 1810 case StartsWith : { 1811 checkContextString(focus, "startsWith"); 1812 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1813 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1814 } 1815 case EndsWith : { 1816 checkContextString(focus, "endsWith"); 1817 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1818 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1819 } 1820 case Matches : { 1821 checkContextString(focus, "matches"); 1822 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1823 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1824 } 1825 case ReplaceMatches : { 1826 checkContextString(focus, "replaceMatches"); 1827 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string")); 1828 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1829 } 1830 case Contains : { 1831 checkContextString(focus, "contains"); 1832 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1833 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1834 } 1835 case Replace : { 1836 checkContextString(focus, "replace"); 1837 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string")); 1838 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1839 } 1840 case Length : { 1841 checkContextPrimitive(focus, "length"); 1842 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1843 } 1844 case Children : 1845 return childTypes(focus, "*"); 1846 case Descendants : 1847 return childTypes(focus, "**"); 1848 case MemberOf : { 1849 checkContextCoded(focus, "memberOf"); 1850 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1851 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1852 } 1853 case Trace : { 1854 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1855 return focus; 1856 } 1857 case Today : 1858 return new TypeDetails(CollectionStatus.SINGLETON, "date"); 1859 case Now : 1860 return new TypeDetails(CollectionStatus.SINGLETON, "dateTime"); 1861 case Resolve : { 1862 checkContextReference(focus, "resolve"); 1863 return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 1864 } 1865 case Extension : { 1866 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1867 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 1868 } 1869 case Custom : { 1870 return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); 1871 } 1872 default: 1873 break; 1874 } 1875 throw new Error("not Implemented yet"); 1876 } 1877 1878 1879 private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 1880 int i = 0; 1881 for (TypeDetails pt : typeSet) { 1882 if (i == paramTypes.size()) 1883 return; 1884 TypeDetails actual = paramTypes.get(i); 1885 i++; 1886 for (String a : actual.getTypes()) { 1887 if (!pt.hasType(worker, a)) 1888 throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 1889 } 1890 } 1891 } 1892 1893 private void checkOrdered(TypeDetails focus, String name) throws PathEngineException { 1894 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) 1895 throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 1896 } 1897 1898 private void checkContextReference(TypeDetails focus, String name) throws PathEngineException { 1899 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference")) 1900 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, Reference"); 1901 } 1902 1903 1904 private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException { 1905 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) 1906 throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept"); 1907 } 1908 1909 1910 private void checkContextString(TypeDetails focus, String name) throws PathEngineException { 1911 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "id")) 1912 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 1913 } 1914 1915 1916 private void checkContextPrimitive(TypeDetails focus, String name) throws PathEngineException { 1917 if (!focus.hasType(primitiveTypes)) 1918 throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()+", not "+focus.describe()); 1919 } 1920 1921 1922 private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException { 1923 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 1924 for (String f : focus.getTypes()) 1925 getChildTypesByName(f, mask, result); 1926 return result; 1927 } 1928 1929 private TypeDetails anything(CollectionStatus status) { 1930 return new TypeDetails(status, allTypes.keySet()); 1931 } 1932 1933 // private boolean isPrimitiveType(String s) { 1934 // 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"); 1935 // } 1936 1937 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 1938 switch (exp.getFunction()) { 1939 case Empty : return funcEmpty(context, focus, exp); 1940 case Not : return funcNot(context, focus, exp); 1941 case Exists : return funcExists(context, focus, exp); 1942 case SubsetOf : return funcSubsetOf(context, focus, exp); 1943 case SupersetOf : return funcSupersetOf(context, focus, exp); 1944 case IsDistinct : return funcIsDistinct(context, focus, exp); 1945 case Distinct : return funcDistinct(context, focus, exp); 1946 case Count : return funcCount(context, focus, exp); 1947 case Where : return funcWhere(context, focus, exp); 1948 case Select : return funcSelect(context, focus, exp); 1949 case All : return funcAll(context, focus, exp); 1950 case Repeat : return funcRepeat(context, focus, exp); 1951 case Item : return funcItem(context, focus, exp); 1952 case As : return funcAs(context, focus, exp); 1953 case Is : return funcIs(context, focus, exp); 1954 case Single : return funcSingle(context, focus, exp); 1955 case First : return funcFirst(context, focus, exp); 1956 case Last : return funcLast(context, focus, exp); 1957 case Tail : return funcTail(context, focus, exp); 1958 case Skip : return funcSkip(context, focus, exp); 1959 case Take : return funcTake(context, focus, exp); 1960 case Iif : return funcIif(context, focus, exp); 1961 case ToInteger : return funcToInteger(context, focus, exp); 1962 case ToDecimal : return funcToDecimal(context, focus, exp); 1963 case ToString : return funcToString(context, focus, exp); 1964 case Substring : return funcSubstring(context, focus, exp); 1965 case StartsWith : return funcStartsWith(context, focus, exp); 1966 case EndsWith : return funcEndsWith(context, focus, exp); 1967 case Matches : return funcMatches(context, focus, exp); 1968 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 1969 case Contains : return funcContains(context, focus, exp); 1970 case Replace : return funcReplace(context, focus, exp); 1971 case Length : return funcLength(context, focus, exp); 1972 case Children : return funcChildren(context, focus, exp); 1973 case Descendants : return funcDescendants(context, focus, exp); 1974 case MemberOf : return funcMemberOf(context, focus, exp); 1975 case Trace : return funcTrace(context, focus, exp); 1976 case Today : return funcToday(context, focus, exp); 1977 case Now : return funcNow(context, focus, exp); 1978 case Resolve: return funcResolve(context, focus, exp); 1979 case Extension: return funcExtension(context, focus, exp); 1980 case Custom: { 1981 List<List<Base>> params = new ArrayList<List<Base>>(); 1982 for (ExpressionNode p : exp.getParameters()) 1983 params.add(execute(context, focus, p, true)); 1984 return hostServices.executeFunction(context.appInfo, exp.getName(), params); 1985 } 1986 default: 1987 throw new Error("not Implemented yet"); 1988 } 1989 } 1990 1991 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 1992 if (exp.getParameters().size() == 1) { 1993 List<Base> result = new ArrayList<Base>(); 1994 List<Base> pc = new ArrayList<Base>(); 1995 boolean all = true; 1996 for (Base item : focus) { 1997 pc.clear(); 1998 pc.add(item); 1999 if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), false))) { 2000 all = false; 2001 break; 2002 } 2003 } 2004 result.add(new BooleanType(all)); 2005 return result; 2006 } else {// (exp.getParameters().size() == 0) { 2007 List<Base> result = new ArrayList<Base>(); 2008 boolean all = true; 2009 for (Base item : focus) { 2010 boolean v = false; 2011 if (item instanceof BooleanType) { 2012 v = ((BooleanType) item).booleanValue(); 2013 } else 2014 v = item != null; 2015 if (!v) { 2016 all = false; 2017 break; 2018 } 2019 } 2020 result.add(new BooleanType(all)); 2021 return result; 2022 } 2023 } 2024 2025 2026 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 2027 return new ExecutionContext(context.appInfo, context.resource, context.context, newThis); 2028 } 2029 2030 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 2031 return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 2032 } 2033 2034 2035 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2036 List<Base> result = new ArrayList<Base>(); 2037 result.add(DateTimeType.now()); 2038 return result; 2039 } 2040 2041 2042 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2043 List<Base> result = new ArrayList<Base>(); 2044 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 2045 return result; 2046 } 2047 2048 2049 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2050 throw new Error("not Implemented yet"); 2051 } 2052 2053 2054 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2055 List<Base> result = new ArrayList<Base>(); 2056 List<Base> current = new ArrayList<Base>(); 2057 current.addAll(focus); 2058 List<Base> added = new ArrayList<Base>(); 2059 boolean more = true; 2060 while (more) { 2061 added.clear(); 2062 for (Base item : current) { 2063 getChildrenByName(item, "*", added); 2064 } 2065 more = !added.isEmpty(); 2066 result.addAll(added); 2067 current.clear(); 2068 current.addAll(added); 2069 } 2070 return result; 2071 } 2072 2073 2074 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2075 List<Base> result = new ArrayList<Base>(); 2076 for (Base b : focus) 2077 getChildrenByName(b, "*", result); 2078 return result; 2079 } 2080 2081 2082 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2083 throw new Error("not Implemented yet"); 2084 } 2085 2086 2087 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2088 List<Base> result = new ArrayList<Base>(); 2089 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2090 2091 if (focus.size() == 1 && !Utilities.noString(sw)) 2092 result.add(new BooleanType(convertToString(focus.get(0)).contains(sw))); 2093 else 2094 result.add(new BooleanType(false)); 2095 return result; 2096 } 2097 2098 2099 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2100 List<Base> result = new ArrayList<Base>(); 2101 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2102 2103 if (focus.size() == 1 && !Utilities.noString(sw)) 2104 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw))); 2105 else 2106 result.add(new BooleanType(false)); 2107 return result; 2108 } 2109 2110 2111 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2112 List<Base> result = new ArrayList<Base>(); 2113 result.add(new StringType(convertToString(focus))); 2114 return result; 2115 } 2116 2117 2118 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2119 String s = convertToString(focus); 2120 List<Base> result = new ArrayList<Base>(); 2121 if (Utilities.isDecimal(s)) 2122 result.add(new DecimalType(s)); 2123 return result; 2124 } 2125 2126 2127 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2128 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2129 Boolean v = convertToBoolean(n1); 2130 2131 if (v) 2132 return execute(context, focus, exp.getParameters().get(1), true); 2133 else if (exp.getParameters().size() < 3) 2134 return new ArrayList<Base>(); 2135 else 2136 return execute(context, focus, exp.getParameters().get(2), true); 2137 } 2138 2139 2140 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2141 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2142 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2143 2144 List<Base> result = new ArrayList<Base>(); 2145 for (int i = 0; i < Math.min(focus.size(), i1); i++) 2146 result.add(focus.get(i)); 2147 return result; 2148 } 2149 2150 2151 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2152 if (focus.size() == 1) 2153 return focus; 2154 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 2155 } 2156 2157 2158 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2159 List<Base> result = new ArrayList<Base>(); 2160 if (focus.size() == 0 || focus.size() > 1) 2161 result.add(new BooleanType(false)); 2162 else { 2163 String tn = exp.getParameters().get(0).getName(); 2164 result.add(new BooleanType(focus.get(0).hasType(tn))); 2165 } 2166 return result; 2167 } 2168 2169 2170 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2171 List<Base> result = new ArrayList<Base>(); 2172 String tn = exp.getParameters().get(0).getName(); 2173 for (Base b : focus) 2174 if (b.hasType(tn)) 2175 result.add(b); 2176 return result; 2177 } 2178 2179 2180 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2181 List<Base> result = new ArrayList<Base>(); 2182 List<Base> current = new ArrayList<Base>(); 2183 current.addAll(focus); 2184 List<Base> added = new ArrayList<Base>(); 2185 boolean more = true; 2186 while (more) { 2187 added.clear(); 2188 List<Base> pc = new ArrayList<Base>(); 2189 for (Base item : current) { 2190 pc.clear(); 2191 pc.add(item); 2192 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 2193 } 2194 more = !added.isEmpty(); 2195 result.addAll(added); 2196 current.clear(); 2197 current.addAll(added); 2198 } 2199 return result; 2200 } 2201 2202 2203 2204 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2205 if (focus.size() <= 1) 2206 return makeBoolean(true); 2207 2208 boolean distinct = true; 2209 for (int i = 0; i < focus.size(); i++) { 2210 for (int j = i+1; j < focus.size(); j++) { 2211 if (doEquals(focus.get(j), focus.get(i))) { 2212 distinct = false; 2213 break; 2214 } 2215 } 2216 } 2217 return makeBoolean(distinct); 2218 } 2219 2220 2221 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2222 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2223 2224 boolean valid = true; 2225 for (Base item : target) { 2226 boolean found = false; 2227 for (Base t : focus) { 2228 if (Base.compareDeep(item, t, false)) { 2229 found = true; 2230 break; 2231 } 2232 } 2233 if (!found) { 2234 valid = false; 2235 break; 2236 } 2237 } 2238 List<Base> result = new ArrayList<Base>(); 2239 result.add(new BooleanType(valid)); 2240 return result; 2241 } 2242 2243 2244 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2245 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2246 2247 boolean valid = true; 2248 for (Base item : focus) { 2249 boolean found = false; 2250 for (Base t : target) { 2251 if (Base.compareDeep(item, t, false)) { 2252 found = true; 2253 break; 2254 } 2255 } 2256 if (!found) { 2257 valid = false; 2258 break; 2259 } 2260 } 2261 List<Base> result = new ArrayList<Base>(); 2262 result.add(new BooleanType(valid)); 2263 return result; 2264 } 2265 2266 2267 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2268 List<Base> result = new ArrayList<Base>(); 2269 result.add(new BooleanType(!focus.isEmpty())); // R2 - can't use ElementUtil 2270 return result; 2271 } 2272 2273 2274 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2275 throw new Error("not Implemented yet"); 2276 } 2277 2278 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2279 List<Base> result = new ArrayList<Base>(); 2280 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2281 String url = nl.get(0).primitiveValue(); 2282 2283 for (Base item : focus) { 2284 List<Base> ext = new ArrayList<Base>(); 2285 getChildrenByName(item, "extension", ext); 2286 getChildrenByName(item, "modifierExtension", ext); 2287 for (Base ex : ext) { 2288 List<Base> vl = new ArrayList<Base>(); 2289 getChildrenByName(ex, "url", vl); 2290 if (convertToString(vl).equals(url)) 2291 result.add(ex); 2292 } 2293 } 2294 return result; 2295 } 2296 2297 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2298 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2299 String name = nl.get(0).primitiveValue(); 2300 2301 log(name, focus); 2302 return focus; 2303 } 2304 2305 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2306 if (focus.size() <= 1) 2307 return focus; 2308 2309 List<Base> result = new ArrayList<Base>(); 2310 for (int i = 0; i < focus.size(); i++) { 2311 boolean found = false; 2312 for (int j = i+1; j < focus.size(); j++) { 2313 if (doEquals(focus.get(j), focus.get(i))) { 2314 found = true; 2315 break; 2316 } 2317 } 2318 if (!found) 2319 result.add(focus.get(i)); 2320 } 2321 return result; 2322 } 2323 2324 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2325 List<Base> result = new ArrayList<Base>(); 2326 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2327 2328 if (focus.size() == 1 && !Utilities.noString(sw)) 2329 result.add(new BooleanType(convertToString(focus.get(0)).matches(sw))); 2330 else 2331 result.add(new BooleanType(false)); 2332 return result; 2333 } 2334 2335 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2336 List<Base> result = new ArrayList<Base>(); 2337 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2338 2339 if (focus.size() == 1 && !Utilities.noString(sw)) 2340 result.add(new BooleanType(convertToString(focus.get(0)).contains(sw))); 2341 else 2342 result.add(new BooleanType(false)); 2343 return result; 2344 } 2345 2346 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2347 List<Base> result = new ArrayList<Base>(); 2348 if (focus.size() == 1) { 2349 String s = convertToString(focus.get(0)); 2350 result.add(new IntegerType(s.length())); 2351 } 2352 return result; 2353 } 2354 2355 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2356 List<Base> result = new ArrayList<Base>(); 2357 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2358 2359 if (focus.size() == 1 && !Utilities.noString(sw)) 2360 result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw))); 2361 else 2362 result.add(new BooleanType(false)); 2363 return result; 2364 } 2365 2366 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2367 List<Base> result = new ArrayList<Base>(); 2368 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2369 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2370 int i2 = -1; 2371 if (exp.parameterCount() == 2) { 2372 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 2373 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 2374 } 2375 2376 if (focus.size() == 1) { 2377 String sw = convertToString(focus.get(0)); 2378 String s; 2379 if (i1 < 0 || i1 >= sw.length()) 2380 return new ArrayList<Base>(); 2381 if (exp.parameterCount() == 2) 2382 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 2383 else 2384 s = sw.substring(i1); 2385 if (!Utilities.noString(s)) 2386 result.add(new StringType(s)); 2387 } 2388 return result; 2389 } 2390 2391 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2392 String s = convertToString(focus); 2393 List<Base> result = new ArrayList<Base>(); 2394 if (Utilities.isInteger(s)) 2395 result.add(new IntegerType(s)); 2396 return result; 2397 } 2398 2399 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2400 List<Base> result = new ArrayList<Base>(); 2401 result.add(new IntegerType(focus.size())); 2402 return result; 2403 } 2404 2405 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2406 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2407 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2408 2409 List<Base> result = new ArrayList<Base>(); 2410 for (int i = i1; i < focus.size(); i++) 2411 result.add(focus.get(i)); 2412 return result; 2413 } 2414 2415 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2416 List<Base> result = new ArrayList<Base>(); 2417 for (int i = 1; i < focus.size(); i++) 2418 result.add(focus.get(i)); 2419 return result; 2420 } 2421 2422 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2423 List<Base> result = new ArrayList<Base>(); 2424 if (focus.size() > 0) 2425 result.add(focus.get(focus.size()-1)); 2426 return result; 2427 } 2428 2429 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2430 List<Base> result = new ArrayList<Base>(); 2431 if (focus.size() > 0) 2432 result.add(focus.get(0)); 2433 return result; 2434 } 2435 2436 2437 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2438 List<Base> result = new ArrayList<Base>(); 2439 List<Base> pc = new ArrayList<Base>(); 2440 for (Base item : focus) { 2441 pc.clear(); 2442 pc.add(item); 2443 if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) 2444 result.add(item); 2445 } 2446 return result; 2447 } 2448 2449 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2450 List<Base> result = new ArrayList<Base>(); 2451 List<Base> pc = new ArrayList<Base>(); 2452 for (Base item : focus) { 2453 pc.clear(); 2454 pc.add(item); 2455 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 2456 } 2457 return result; 2458 } 2459 2460 2461 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2462 List<Base> result = new ArrayList<Base>(); 2463 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2464 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 2465 result.add(focus.get(Integer.parseInt(s))); 2466 return result; 2467 } 2468 2469 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2470 List<Base> result = new ArrayList<Base>(); 2471 result.add(new BooleanType(focus.isEmpty())); 2472 return result; 2473 } 2474 2475 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2476 return makeBoolean(!convertToBoolean(focus)); 2477 } 2478 2479 public class ElementDefinitionMatch { 2480 private ElementDefinition definition; 2481 private String fixedType; 2482 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 2483 super(); 2484 this.definition = definition; 2485 this.fixedType = fixedType; 2486 } 2487 public ElementDefinition getDefinition() { 2488 return definition; 2489 } 2490 public String getFixedType() { 2491 return fixedType; 2492 } 2493 2494 } 2495 2496 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 2497 if (Utilities.noString(type)) 2498 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 2499 if (type.equals("xhtml")) 2500 return; 2501 String url = null; 2502 if (type.contains(".")) { 2503 url = "http://hl7.org/fhir/StructureDefinition/"+type.substring(0, type.indexOf(".")); 2504 } else { 2505 url = "http://hl7.org/fhir/StructureDefinition/"+type; 2506 } 2507 String tail = ""; 2508 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 2509 if (sd == null) 2510 throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 2511 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 2512 ElementDefinitionMatch m = null; 2513 if (type.contains(".")) 2514 m = getElementDefinition(sd, type, false); 2515 if (m != null && hasDataType(m.definition)) { 2516 if (m.fixedType != null) 2517 { 2518 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+m.fixedType); 2519 if (dt == null) 2520 throw new DefinitionException("unknown data type "+m.fixedType); 2521 sdl.add(dt); 2522 } else 2523 for (TypeRefComponent t : m.definition.getType()) { 2524 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t.getCode()); 2525 if (dt == null) 2526 throw new DefinitionException("unknown data type "+t.getCode()); 2527 sdl.add(dt); 2528 } 2529 } else { 2530 sdl.add(sd); 2531 if (type.contains(".")) 2532 tail = type.substring(type.indexOf(".")); 2533 } 2534 2535 for (StructureDefinition sdi : sdl) { 2536 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 2537 if (name.equals("**")) { 2538 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2539 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2540 if (ed.getPath().startsWith(path)) 2541 for (TypeRefComponent t : ed.getType()) { 2542 if (t.hasCode() && t.getCodeElement().hasValue()) { 2543 String tn = null; 2544 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2545 tn = ed.getPath(); 2546 else 2547 tn = t.getCode(); 2548 if (t.getCode().equals("Resource")) { 2549 for (String rn : worker.getResourceNames()) { 2550 if (!result.hasType(worker, rn)) { 2551 result.addType(rn); 2552 getChildTypesByName(rn, "**", result); 2553 } 2554 } 2555 } else if (!result.hasType(worker, tn)) { 2556 result.addType(tn); 2557 getChildTypesByName(tn, "**", result); 2558 } 2559 } 2560 } 2561 } 2562 } else if (name.equals("*")) { 2563 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2564 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2565 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 2566 for (TypeRefComponent t : ed.getType()) { 2567 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2568 result.addType(ed.getPath()); 2569 else if (t.getCode().equals("Resource")) 2570 result.addTypes(worker.getResourceNames()); 2571 else 2572 result.addType(t.getCode()); 2573 } 2574 } 2575 } else { 2576 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 2577 2578 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 2579 if (ed != null) { 2580 if (!Utilities.noString(ed.getFixedType())) 2581 result.addType(ed.getFixedType()); 2582 else 2583 for (TypeRefComponent t : ed.getDefinition().getType()) { 2584 if (Utilities.noString(t.getCode())) 2585 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 2586 2587 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2588 result.addType(path); 2589 else if (t.getCode().equals("Resource")) 2590 result.addTypes(worker.getResourceNames()); 2591 else 2592 result.addType(t.getCode()); 2593 } 2594 } 2595 } 2596 } 2597 } 2598 2599 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 2600 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2601 if (ed.getPath().equals(path)) { 2602 if (ed.hasNameReference()) { 2603 return getElementDefinitionByName(sd, ed.getNameReference()); 2604 } else 2605 return new ElementDefinitionMatch(ed, null); 2606 } 2607 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) 2608 return new ElementDefinitionMatch(ed, null); 2609 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 2610 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 2611 if (primitiveTypes.contains(s)) 2612 return new ElementDefinitionMatch(ed, s); 2613 else 2614 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); 2615 } 2616 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 2617 // now we walk into the type. 2618 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 2619 throw new PathEngineException("Internal typing issue...."); 2620 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode()); 2621 if (nsd == null) 2622 throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode()); 2623 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName); 2624 } 2625 if (ed.hasNameReference() && path.startsWith(ed.getPath()+".")) { 2626 ElementDefinitionMatch m = getElementDefinitionByName(sd, ed.getNameReference()); 2627 return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName); 2628 } 2629 } 2630 return null; 2631 } 2632 2633 private boolean isAbstractType(List<TypeRefComponent> list) { 2634 return list.size() != 1 ? false : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 2635} 2636 2637 2638 private boolean hasType(ElementDefinition ed, String s) { 2639 for (TypeRefComponent t : ed.getType()) 2640 if (s.equalsIgnoreCase(t.getCode())) 2641 return true; 2642 return false; 2643 } 2644 2645 private boolean hasDataType(ElementDefinition ed) { 2646 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 2647 } 2648 2649 private ElementDefinitionMatch getElementDefinitionByName(StructureDefinition sd, String ref) { 2650 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2651 if (ref.equals(ed.getName())) 2652 return new ElementDefinitionMatch(ed, null); 2653 } 2654 return null; 2655 } 2656 2657 2658 public boolean hasLog() { 2659 return log != null && log.length() > 0; 2660 } 2661 2662 2663 public String takeLog() { 2664 if (!hasLog()) 2665 return ""; 2666 String s = log.toString(); 2667 log = new StringBuilder(); 2668 return s; 2669 } 2670 2671}