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