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