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