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