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