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