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