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