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