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