001package org.hl7.fhir.r4.utils; 002 003import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 004import ca.uhn.fhir.util.ElementUtil; 005import org.apache.commons.lang3.NotImplementedException; 006import org.fhir.ucum.Decimal; 007import org.fhir.ucum.Pair; 008import org.fhir.ucum.UcumException; 009import org.hl7.fhir.exceptions.DefinitionException; 010import org.hl7.fhir.exceptions.FHIRException; 011import org.hl7.fhir.exceptions.PathEngineException; 012import org.hl7.fhir.r4.conformance.ProfileUtilities; 013import org.hl7.fhir.r4.context.IWorkerContext; 014import org.hl7.fhir.r4.model.*; 015import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 016import org.hl7.fhir.r4.model.ExpressionNode.*; 017import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 018import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 019import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; 020import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; 021import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; 022import org.hl7.fhir.utilities.TerminologyServiceOptions; 023import org.hl7.fhir.utilities.Utilities; 024 025import java.math.BigDecimal; 026import java.util.*; 027 028/*- 029 * #%L 030 * org.hl7.fhir.r4 031 * %% 032 * Copyright (C) 2014 - 2019 Health Level 7 033 * %% 034 * Licensed under the Apache License, Version 2.0 (the "License"); 035 * you may not use this file except in compliance with the License. 036 * You may obtain a copy of the License at 037 * 038 * http://www.apache.org/licenses/LICENSE-2.0 039 * 040 * Unless required by applicable law or agreed to in writing, software 041 * distributed under the License is distributed on an "AS IS" BASIS, 042 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 043 * See the License for the specific language governing permissions and 044 * limitations under the License. 045 * #L% 046 */ 047 048/** 049 * 050 * @author Grahame Grieve 051 * 052 */ 053public class FHIRPathEngine { 054 private enum Equality { Null, True, False } 055 056 private class FHIRConstant extends Base { 057 058 private static final long serialVersionUID = -8933773658248269439L; 059 private String value; 060 061 public FHIRConstant(String value) { 062 this.value = value; 063 } 064 065 @Override 066 public String fhirType() { 067 return "%constant"; 068 } 069 070 @Override 071 protected void listChildren(List<Property> result) { 072 } 073 074 @Override 075 public String getIdBase() { 076 return null; 077 } 078 079 @Override 080 public void setIdBase(String value) { 081 } 082 083 public String getValue() { 084 return value; 085 } 086 087 @Override 088 public String primitiveValue() { 089 return value; 090 } 091 } 092 093 private class ClassTypeInfo extends Base { 094 private static final long serialVersionUID = 4909223114071029317L; 095 private Base instance; 096 097 public ClassTypeInfo(Base instance) { 098 super(); 099 this.instance = instance; 100 } 101 102 @Override 103 public String fhirType() { 104 return "ClassInfo"; 105 } 106 107 @Override 108 protected void listChildren(List<Property> result) { 109 } 110 111 @Override 112 public String getIdBase() { 113 return null; 114 } 115 116 @Override 117 public void setIdBase(String value) { 118 } 119 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 120 if (name.equals("name")) 121 return new Base[]{new StringType(getName())}; 122 else if (name.equals("namespace")) 123 return new Base[]{new StringType(getNamespace())}; 124 else 125 return super.getProperty(hash, name, checkValid); 126 } 127 128 private String getNamespace() { 129 if ((instance instanceof Resource)) 130 return "FHIR"; 131 else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) 132 return "System"; 133 else 134 return "FHIR"; 135 } 136 137 private String getName() { 138 if ((instance instanceof Resource)) 139 return instance.fhirType(); 140 else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) 141 return Utilities.capitalize(instance.fhirType()); 142 else 143 return instance.fhirType(); 144 } 145 } 146 147 private IWorkerContext worker; 148 private IEvaluationContext hostServices; 149 private StringBuilder log = new StringBuilder(); 150 private Set<String> primitiveTypes = new HashSet<String>(); 151 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 152 private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for emptty sets, so when running for R2/R3, this is set ot true 153 private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(); 154 155 // if the fhir path expressions are allowed to use constants beyond those defined in the specification 156 // the application can implement them by providing a constant resolver 157 public interface IEvaluationContext { 158 public class FunctionDetails { 159 private String description; 160 private int minParameters; 161 private int maxParameters; 162 public FunctionDetails(String description, int minParameters, int maxParameters) { 163 super(); 164 this.description = description; 165 this.minParameters = minParameters; 166 this.maxParameters = maxParameters; 167 } 168 public String getDescription() { 169 return description; 170 } 171 public int getMinParameters() { 172 return minParameters; 173 } 174 public int getMaxParameters() { 175 return maxParameters; 176 } 177 178 } 179 180 /** 181 * A constant reference - e.g. a reference to a name that must be resolved in context. 182 * The % will be removed from the constant name before this is invoked. 183 * 184 * This will also be called if the host invokes the FluentPath engine with a context of null 185 * 186 * @param appContext - content passed into the fluent path engine 187 * @param name - name reference to resolve 188 * @param beforeContext - whether this is being called before the name is resolved locally, or not 189 * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired) 190 */ 191 public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException; 192 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException; 193 194 /** 195 * when the .log() function is called 196 * 197 * @param argument 198 * @param focus 199 * @return 200 */ 201 public boolean log(String argument, List<Base> focus); 202 203 // extensibility for functions 204 /** 205 * 206 * @param functionName 207 * @return null if the function is not known 208 */ 209 public FunctionDetails resolveFunction(String functionName); 210 211 /** 212 * Check the function parameters, and throw an error if they are incorrect, or return the type for the function 213 * @param functionName 214 * @param parameters 215 * @return 216 */ 217 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException; 218 219 /** 220 * @param appContext 221 * @param functionName 222 * @param parameters 223 * @return 224 */ 225 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters); 226 227 /** 228 * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null 229 * @appContext - passed in by the host to the FHIRPathEngine 230 * @param url the reference (Reference.reference or the value of the canonical 231 * @return 232 * @throws FHIRException 233 */ 234 public Base resolveReference(Object appContext, String url) throws FHIRException; 235 236 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException; 237 238 /* 239 * return the value set referenced by the url, which has been used in memberOf() 240 */ 241 public ValueSet resolveValueSet(Object appContext, String url); 242 } 243 244 245 /** 246 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 247 */ 248 public FHIRPathEngine(IWorkerContext worker) { 249 super(); 250 this.worker = worker; 251 for (StructureDefinition sd : worker.allStructures()) { 252 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) 253 allTypes.put(sd.getName(), sd); 254 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 255 primitiveTypes.add(sd.getName()); 256 } 257 } 258 } 259 260 261 // --- 3 methods to override in children ------------------------------------------------------- 262 // if you don't override, it falls through to the using the base reference implementation 263 // HAPI overrides to these to support extending the base model 264 265 public IEvaluationContext getHostServices() { 266 return hostServices; 267 } 268 269 270 public void setHostServices(IEvaluationContext constantResolver) { 271 this.hostServices = constantResolver; 272 } 273 274 275 /** 276 * Given an item, return all the children that conform to the pattern described in name 277 * 278 * Possible patterns: 279 * - a simple name (which may be the base of a name with [] e.g. value[x]) 280 * - a name with a type replacement e.g. valueCodeableConcept 281 * - * which means all children 282 * - ** which means all descendants 283 * 284 * @param item 285 * @param name 286 * @param result 287 * @throws FHIRException 288 */ 289 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 290 Base[] list = item.listChildrenByName(name, false); 291 if (list != null) 292 for (Base v : list) 293 if (v != null) 294 result.add(v); 295 } 296 297 298 public boolean isLegacyMode() { 299 return legacyMode; 300 } 301 302 303 public void setLegacyMode(boolean legacyMode) { 304 this.legacyMode = legacyMode; 305 } 306 307 308 // --- public API ------------------------------------------------------- 309 /** 310 * Parse a path for later use using execute 311 * 312 * @param path 313 * @return 314 * @throws PathEngineException 315 * @throws Exception 316 */ 317 public ExpressionNode parse(String path) throws FHIRLexerException { 318 return parse(path, null); 319 } 320 321 public ExpressionNode parse(String path, String name) throws FHIRLexerException { 322 FHIRLexer lexer = new FHIRLexer(path, name); 323 if (lexer.done()) 324 throw lexer.error("Path cannot be empty"); 325 ExpressionNode result = parseExpression(lexer, true); 326 if (!lexer.done()) 327 throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); 328 result.check(); 329 return result; 330 } 331 332 public static class ExpressionNodeWithOffset { 333 private int offset; 334 private ExpressionNode node; 335 public ExpressionNodeWithOffset(int offset, ExpressionNode node) { 336 super(); 337 this.offset = offset; 338 this.node = node; 339 } 340 public int getOffset() { 341 return offset; 342 } 343 public ExpressionNode getNode() { 344 return node; 345 } 346 347 } 348 /** 349 * Parse a path for later use using execute 350 * 351 * @param path 352 * @return 353 * @throws PathEngineException 354 * @throws Exception 355 */ 356 public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { 357 FHIRLexer lexer = new FHIRLexer(path, i); 358 if (lexer.done()) 359 throw lexer.error("Path cannot be empty"); 360 ExpressionNode result = parseExpression(lexer, true); 361 result.check(); 362 return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); 363 } 364 365 /** 366 * Parse a path that is part of some other syntax 367 * 368 * @return 369 * @throws PathEngineException 370 * @throws Exception 371 */ 372 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 373 ExpressionNode result = parseExpression(lexer, true); 374 result.check(); 375 return result; 376 } 377 378 /** 379 * check that paths referred to in the ExpressionNode are valid 380 * 381 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 382 * 383 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 384 * 385 * @param context - the logical type against which this path is applied 386 * @throws DefinitionException 387 * @throws PathEngineException 388 * @if the path is not valid 389 */ 390 public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 391 // if context is a path that refers to a type, do that conversion now 392 TypeDetails types; 393 if (context == null) { 394 types = null; // this is a special case; the first path reference will have to resolve to something in the context 395 } else if (!context.contains(".")) { 396 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context); 397 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 398 } else { 399 String ctxt = context.substring(0, context.indexOf('.')); 400 if (Utilities.isAbsoluteUrl(resourceType)) { 401 ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt; 402 } 403 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); 404 if (sd == null) 405 throw new PathEngineException("Unknown context "+context); 406 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 407 if (ed == null) 408 throw new PathEngineException("Unknown context element "+context); 409 if (ed.fixedType != null) 410 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 411 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 412 types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context); 413 else { 414 types = new TypeDetails(CollectionStatus.SINGLETON); 415 for (TypeRefComponent t : ed.getDefinition().getType()) 416 types.addType(t.getCode()); 417 } 418 } 419 420 return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true); 421 } 422 423 public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 424 // if context is a path that refers to a type, do that conversion now 425 TypeDetails types; 426 if (!context.contains(".")) { 427 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 428 } else { 429 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 430 if (ed == null) 431 throw new PathEngineException("Unknown context element "+context); 432 if (ed.fixedType != null) 433 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 434 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 435 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); 436 else { 437 types = new TypeDetails(CollectionStatus.SINGLETON); 438 for (TypeRefComponent t : ed.getDefinition().getType()) 439 types.addType(t.getCode()); 440 } 441 } 442 443 return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true); 444 } 445 446 public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 447 // if context is a path that refers to a type, do that conversion now 448 TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context 449 return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true); 450 } 451 452 public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 453 return check(appContext, resourceType, context, parse(expr)); 454 } 455 456 private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { 457 String dateLeftString = theL.primitiveValue(); 458 DateTimeType dateLeft = new DateTimeType(dateLeftString); 459 460 String dateRightString = theR.primitiveValue(); 461 DateTimeType dateRight = new DateTimeType(dateRightString); 462 463 if (theEquivalenceTest) { 464 return dateLeft.equalsUsingFhirPathRules(dateRight) == Boolean.TRUE ? 0 : 1; 465 } 466 467 if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 468 dateLeft.setTimeZoneZulu(true); 469 } 470 if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 471 dateRight.setTimeZoneZulu(true); 472 } 473 474 dateLeftString = dateLeft.getValueAsString(); 475 dateRightString = dateRight.getValueAsString(); 476 477 return dateLeftString.compareTo(dateRightString); 478 } 479 480 /** 481 * evaluate a path and return the matching elements 482 * 483 * @param base - the object against which the path is being evaluated 484 * @param ExpressionNode - the parsed ExpressionNode statement to use 485 * @return 486 * @throws FHIRException 487 * @ 488 */ 489 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { 490 List<Base> list = new ArrayList<Base>(); 491 if (base != null) 492 list.add(base); 493 log = new StringBuilder(); 494 return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); 495 } 496 497 /** 498 * evaluate a path and return the matching elements 499 * 500 * @param base - the object against which the path is being evaluated 501 * @param path - the FHIR Path statement to use 502 * @return 503 * @throws FHIRException 504 * @ 505 */ 506 public List<Base> evaluate(Base base, String path) throws FHIRException { 507 ExpressionNode exp = parse(path); 508 List<Base> list = new ArrayList<Base>(); 509 if (base != null) 510 list.add(base); 511 log = new StringBuilder(); 512 return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), list, exp, true); 513 } 514 515 /** 516 * evaluate a path and return the matching elements 517 * 518 * @param base - the object against which the path is being evaluated 519 * @param ExpressionNode - the parsed ExpressionNode statement to use 520 * @return 521 * @throws FHIRException 522 * @ 523 */ 524 public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 525 List<Base> list = new ArrayList<Base>(); 526 if (base != null) 527 list.add(base); 528 log = new StringBuilder(); 529 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, ExpressionNode, true); 530 } 531 532 /** 533 * evaluate a path and return the matching elements 534 * 535 * @param base - the object against which the path is being evaluated 536 * @param ExpressionNode - the parsed ExpressionNode statement to use 537 * @return 538 * @throws FHIRException 539 * @ 540 */ 541 public List<Base> evaluate(Object appContext, Base focusResource, Base rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 542 List<Base> list = new ArrayList<Base>(); 543 if (base != null) 544 list.add(base); 545 log = new StringBuilder(); 546 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, ExpressionNode, true); 547 } 548 549 /** 550 * evaluate a path and return the matching elements 551 * 552 * @param base - the object against which the path is being evaluated 553 * @param path - the FHIR Path statement to use 554 * @return 555 * @throws FHIRException 556 * @ 557 */ 558 public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { 559 ExpressionNode exp = parse(path); 560 List<Base> list = new ArrayList<Base>(); 561 if (base != null) 562 list.add(base); 563 log = new StringBuilder(); 564 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true); 565 } 566 567 /** 568 * evaluate a path and return true or false (e.g. for an invariant) 569 * 570 * @param base - the object against which the path is being evaluated 571 * @param path - the FHIR Path statement to use 572 * @return 573 * @throws FHIRException 574 * @ 575 */ 576 public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { 577 return convertToBoolean(evaluate(null, focusResource, rootResource, base, path)); 578 } 579 580 /** 581 * evaluate a path and return true or false (e.g. for an invariant) 582 * 583 * @param base - the object against which the path is being evaluated 584 * @return 585 * @throws FHIRException 586 * @ 587 */ 588 public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { 589 return convertToBoolean(evaluate(null, focusResource, rootResource, base, node)); 590 } 591 592 /** 593 * evaluate a path and return true or false (e.g. for an invariant) 594 * 595 * @param appInfo - application context 596 * @param base - the object against which the path is being evaluated 597 * @return 598 * @throws FHIRException 599 * @ 600 */ 601 public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { 602 return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); 603 } 604 605 /** 606 * evaluate a path and return true or false (e.g. for an invariant) 607 * 608 * @param base - the object against which the path is being evaluated 609 * @return 610 * @throws FHIRException 611 * @ 612 */ 613 public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { 614 return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); 615 } 616 617 /** 618 * evaluate a path and a string containing the outcome (for display) 619 * 620 * @param base - the object against which the path is being evaluated 621 * @param path - the FHIR Path statement to use 622 * @return 623 * @throws FHIRException 624 * @ 625 */ 626 public String evaluateToString(Base base, String path) throws FHIRException { 627 return convertToString(evaluate(base, path)); 628 } 629 630 public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { 631 return convertToString(evaluate(appInfo, focusResource, rootResource, base, node)); 632 } 633 634 /** 635 * worker routine for converting a set of objects to a string representation 636 * 637 * @param items - result from @evaluate 638 * @return 639 */ 640 public String convertToString(List<Base> items) { 641 StringBuilder b = new StringBuilder(); 642 boolean first = true; 643 for (Base item : items) { 644 if (first) 645 first = false; 646 else 647 b.append(','); 648 649 b.append(convertToString(item)); 650 } 651 return b.toString(); 652 } 653 654 public String convertToString(Base item) { 655 if (item.isPrimitive()) 656 return item.primitiveValue(); 657 else if (item instanceof Quantity) { 658 Quantity q = (Quantity) item; 659 if (q.getSystem().equals("http://unitsofmeasure.org")) { 660 String u = "'"+q.getCode()+"'"; 661 return q.getValue().toPlainString()+" "+u; 662 } 663 else 664 return item.toString(); 665 } else 666 return item.toString(); 667 } 668 669 /** 670 * worker routine for converting a set of objects to a boolean representation (for invariants) 671 * 672 * @param items - result from @evaluate 673 * @return 674 */ 675 public boolean convertToBoolean(List<Base> items) { 676 if (items == null) 677 return false; 678 else if (items.size() == 1 && items.get(0) instanceof BooleanType) 679 return ((BooleanType) items.get(0)).getValue(); 680 else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model 681 return Boolean.valueOf(items.get(0).primitiveValue()); 682 else 683 return items.size() > 0; 684 } 685 686 687 private void log(String name, List<Base> contents) { 688 if (hostServices == null || !hostServices.log(name, contents)) { 689 if (log.length() > 0) 690 log.append("; "); 691 log.append(name); 692 log.append(": "); 693 boolean first = true; 694 for (Base b : contents) { 695 if (first) 696 first = false; 697 else 698 log.append(","); 699 log.append(convertToString(b)); 700 } 701 } 702 } 703 704 public String forLog() { 705 if (log.length() > 0) 706 return " ("+log.toString()+")"; 707 else 708 return ""; 709 } 710 711 private class ExecutionContext { 712 private Object appInfo; 713 private Base focusResource; 714 private Base rootResource; 715 private Base context; 716 private Base thisItem; 717 private List<Base> total; 718 private Map<String, Base> aliases; 719 720 public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map<String, Base> aliases, Base thisItem) { 721 this.appInfo = appInfo; 722 this.context = context; 723 this.focusResource = resource; 724 this.rootResource = rootResource; 725 this.aliases = aliases; 726 this.thisItem = thisItem; 727 } 728 public Base getFocusResource() { 729 return focusResource; 730 } 731 public Base getRootResource() { 732 return rootResource; 733 } 734 public Base getThisItem() { 735 return thisItem; 736 } 737 public List<Base> getTotal() { 738 return total; 739 } 740 public void addAlias(String name, List<Base> focus) throws FHIRException { 741 if (aliases == null) 742 aliases = new HashMap<String, Base>(); 743 else 744 aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 745 if (focus.size() > 1) 746 throw new FHIRException("Attempt to alias a collection, not a singleton"); 747 aliases.put(name, focus.size() == 0 ? null : focus.get(0)); 748 } 749 public Base getAlias(String name) { 750 return aliases == null ? null : aliases.get(name); 751 } 752 } 753 754 private class ExecutionTypeContext { 755 private Object appInfo; 756 private String resource; 757 private TypeDetails context; 758 private TypeDetails thisItem; 759 private TypeDetails total; 760 761 762 public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) { 763 super(); 764 this.appInfo = appInfo; 765 this.resource = resource; 766 this.context = context; 767 this.thisItem = thisItem; 768 769 } 770 public String getResource() { 771 return resource; 772 } 773 public TypeDetails getThisItem() { 774 return thisItem; 775 } 776 777 778 } 779 780 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 781 ExpressionNode result = new ExpressionNode(lexer.nextId()); 782 ExpressionNode wrapper = null; 783 SourceLocation c = lexer.getCurrentStartLocation(); 784 result.setStart(lexer.getCurrentLocation()); 785 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 786 // so we back correct for both +/- and as part of a numeric constant below. 787 788 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 789 // so we back correct for both +/- and as part of a numeric constant below. 790 if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) { 791 wrapper = new ExpressionNode(lexer.nextId()); 792 wrapper.setKind(Kind.Unary); 793 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take())); 794 wrapper.setProximal(proximal); 795 } 796 797 if (lexer.isConstant()) { 798 boolean isString = lexer.isStringConstant(); 799 if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) { 800 // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations 801 wrapper = new ExpressionNode(lexer.nextId()); 802 wrapper.setKind(Kind.Unary); 803 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1))); 804 wrapper.setProximal(proximal); 805 lexer.setCurrent(lexer.getCurrent().substring(1)); 806 } 807 result.setConstant(processConstant(lexer)); 808 result.setKind(Kind.Constant); 809 if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) { 810 // it's a quantity 811 String ucum = null; 812 if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { 813 String s = lexer.take(); 814 if (s.equals("year") || s.equals("years")) 815 ucum = "a"; 816 else if (s.equals("month") || s.equals("months")) 817 ucum = "mo"; 818 else if (s.equals("week") || s.equals("weeks")) 819 ucum = "wk"; 820 else if (s.equals("day") || s.equals("days")) 821 ucum = "d"; 822 else if (s.equals("hour") || s.equals("hours")) 823 ucum = "h"; 824 else if (s.equals("minute") || s.equals("minutes")) 825 ucum = "min"; 826 else if (s.equals("second") || s.equals("seconds")) 827 ucum = "s"; 828 else // (s.equals("millisecond") || s.equals("milliseconds")) 829 ucum = "ms"; 830 } else 831 ucum = lexer.readConstant("units"); 832 result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum)); 833 } 834 result.setEnd(lexer.getCurrentLocation()); 835 } else if ("(".equals(lexer.getCurrent())) { 836 lexer.next(); 837 result.setKind(Kind.Group); 838 result.setGroup(parseExpression(lexer, true)); 839 if (!")".equals(lexer.getCurrent())) 840 throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); 841 result.setEnd(lexer.getCurrentLocation()); 842 lexer.next(); 843 } else { 844 if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) 845 throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); 846 if (lexer.isFixedName()) 847 result.setName(lexer.readFixedName("Path Name")); 848 else 849 result.setName(lexer.take()); 850 result.setEnd(lexer.getCurrentLocation()); 851 if (!result.checkName()) 852 throw lexer.error("Found "+result.getName()+" expecting a valid token name"); 853 if ("(".equals(lexer.getCurrent())) { 854 Function f = Function.fromCode(result.getName()); 855 FunctionDetails details = null; 856 if (f == null) { 857 if (hostServices != null) 858 details = hostServices.resolveFunction(result.getName()); 859 if (details == null) 860 throw lexer.error("The name "+result.getName()+" is not a valid function name"); 861 f = Function.Custom; 862 } 863 result.setKind(Kind.Function); 864 result.setFunction(f); 865 lexer.next(); 866 while (!")".equals(lexer.getCurrent())) { 867 result.getParameters().add(parseExpression(lexer, true)); 868 if (",".equals(lexer.getCurrent())) 869 lexer.next(); 870 else if (!")".equals(lexer.getCurrent())) 871 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); 872 } 873 result.setEnd(lexer.getCurrentLocation()); 874 lexer.next(); 875 checkParameters(lexer, c, result, details); 876 } else 877 result.setKind(Kind.Name); 878 } 879 ExpressionNode focus = result; 880 if ("[".equals(lexer.getCurrent())) { 881 lexer.next(); 882 ExpressionNode item = new ExpressionNode(lexer.nextId()); 883 item.setKind(Kind.Function); 884 item.setFunction(ExpressionNode.Function.Item); 885 item.getParameters().add(parseExpression(lexer, true)); 886 if (!lexer.getCurrent().equals("]")) 887 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); 888 lexer.next(); 889 result.setInner(item); 890 focus = item; 891 } 892 if (".".equals(lexer.getCurrent())) { 893 lexer.next(); 894 focus.setInner(parseExpression(lexer, false)); 895 } 896 result.setProximal(proximal); 897 if (proximal) { 898 while (lexer.isOp()) { 899 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 900 focus.setOpStart(lexer.getCurrentStartLocation()); 901 focus.setOpEnd(lexer.getCurrentLocation()); 902 lexer.next(); 903 focus.setOpNext(parseExpression(lexer, false)); 904 focus = focus.getOpNext(); 905 } 906 result = organisePrecedence(lexer, result); 907 } 908 if (wrapper != null) { 909 wrapper.setOpNext(result); 910 result.setProximal(false); 911 result = wrapper; 912 } 913 return result; 914 } 915 916 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 917 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 918 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 919 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 920 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 921 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); 922 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 923 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 924 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 925 // last: implies 926 return node; 927 } 928 929 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 930 // work : boolean; 931 // focus, node, group : ExpressionNode; 932 933 assert(start.isProximal()); 934 935 // is there anything to do? 936 boolean work = false; 937 ExpressionNode focus = start.getOpNext(); 938 if (ops.contains(start.getOperation())) { 939 while (focus != null && focus.getOperation() != null) { 940 work = work || !ops.contains(focus.getOperation()); 941 focus = focus.getOpNext(); 942 } 943 } else { 944 while (focus != null && focus.getOperation() != null) { 945 work = work || ops.contains(focus.getOperation()); 946 focus = focus.getOpNext(); 947 } 948 } 949 if (!work) 950 return start; 951 952 // entry point: tricky 953 ExpressionNode group; 954 if (ops.contains(start.getOperation())) { 955 group = newGroup(lexer, start); 956 group.setProximal(true); 957 focus = start; 958 start = group; 959 } else { 960 ExpressionNode node = start; 961 962 focus = node.getOpNext(); 963 while (!ops.contains(focus.getOperation())) { 964 node = focus; 965 focus = focus.getOpNext(); 966 } 967 group = newGroup(lexer, focus); 968 node.setOpNext(group); 969 } 970 971 // now, at this point: 972 // group is the group we are adding to, it already has a .group property filled out. 973 // focus points at the group.group 974 do { 975 // run until we find the end of the sequence 976 while (ops.contains(focus.getOperation())) 977 focus = focus.getOpNext(); 978 if (focus.getOperation() != null) { 979 group.setOperation(focus.getOperation()); 980 group.setOpNext(focus.getOpNext()); 981 focus.setOperation(null); 982 focus.setOpNext(null); 983 // now look for another sequence, and start it 984 ExpressionNode node = group; 985 focus = group.getOpNext(); 986 if (focus != null) { 987 while (focus != null && !ops.contains(focus.getOperation())) { 988 node = focus; 989 focus = focus.getOpNext(); 990 } 991 if (focus != null) { // && (focus.Operation in Ops) - must be true 992 group = newGroup(lexer, focus); 993 node.setOpNext(group); 994 } 995 } 996 } 997 } 998 while (focus != null && focus.getOperation() != null); 999 return start; 1000 } 1001 1002 1003 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 1004 ExpressionNode result = new ExpressionNode(lexer.nextId()); 1005 result.setKind(Kind.Group); 1006 result.setGroup(next); 1007 result.getGroup().setProximal(true); 1008 return result; 1009 } 1010 1011 private Base processConstant(FHIRLexer lexer) throws FHIRLexerException { 1012 if (lexer.isStringConstant()) { 1013 return new StringType(processConstantString(lexer.take(), lexer)).noExtensions(); 1014 } else if (Utilities.isInteger(lexer.getCurrent())) { 1015 return new IntegerType(lexer.take()).noExtensions(); 1016 } else if (Utilities.isDecimal(lexer.getCurrent(), false)) { 1017 return new DecimalType(lexer.take()).noExtensions(); 1018 } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) { 1019 return new BooleanType(lexer.take()).noExtensions(); 1020 } else if (lexer.getCurrent().equals("{}")) { 1021 lexer.take(); 1022 return null; 1023 } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { 1024 return new FHIRConstant(lexer.take()); 1025 } else 1026 throw lexer.error("Invalid Constant "+lexer.getCurrent()); 1027 } 1028 1029 // procedure CheckParamCount(c : integer); 1030 // begin 1031 // if exp.Parameters.Count <> c then 1032 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 1033 // end; 1034 1035 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 1036 if (exp.getParameters().size() != count) 1037 throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString()); 1038 return true; 1039 } 1040 1041 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 1042 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) 1043 throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString()); 1044 return true; 1045 } 1046 1047 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 1048 switch (exp.getFunction()) { 1049 case Empty: return checkParamCount(lexer, location, exp, 0); 1050 case Not: return checkParamCount(lexer, location, exp, 0); 1051 case Exists: return checkParamCount(lexer, location, exp, 0); 1052 case SubsetOf: return checkParamCount(lexer, location, exp, 1); 1053 case SupersetOf: return checkParamCount(lexer, location, exp, 1); 1054 case IsDistinct: return checkParamCount(lexer, location, exp, 0); 1055 case Distinct: return checkParamCount(lexer, location, exp, 0); 1056 case Count: return checkParamCount(lexer, location, exp, 0); 1057 case Where: return checkParamCount(lexer, location, exp, 1); 1058 case Select: return checkParamCount(lexer, location, exp, 1); 1059 case All: return checkParamCount(lexer, location, exp, 0, 1); 1060 case Repeat: return checkParamCount(lexer, location, exp, 1); 1061 case Aggregate: return checkParamCount(lexer, location, exp, 1, 2); 1062 case Item: return checkParamCount(lexer, location, exp, 1); 1063 case As: return checkParamCount(lexer, location, exp, 1); 1064 case OfType: return checkParamCount(lexer, location, exp, 1); 1065 case Type: return checkParamCount(lexer, location, exp, 0); 1066 case Is: return checkParamCount(lexer, location, exp, 1); 1067 case Single: return checkParamCount(lexer, location, exp, 0); 1068 case First: return checkParamCount(lexer, location, exp, 0); 1069 case Last: return checkParamCount(lexer, location, exp, 0); 1070 case Tail: return checkParamCount(lexer, location, exp, 0); 1071 case Skip: return checkParamCount(lexer, location, exp, 1); 1072 case Take: return checkParamCount(lexer, location, exp, 1); 1073 case Union: return checkParamCount(lexer, location, exp, 1); 1074 case Combine: return checkParamCount(lexer, location, exp, 1); 1075 case Intersect: return checkParamCount(lexer, location, exp, 1); 1076 case Exclude: return checkParamCount(lexer, location, exp, 1); 1077 case Iif: return checkParamCount(lexer, location, exp, 2,3); 1078 case Lower: return checkParamCount(lexer, location, exp, 0); 1079 case Upper: return checkParamCount(lexer, location, exp, 0); 1080 case ToChars: return checkParamCount(lexer, location, exp, 0); 1081 case Substring: return checkParamCount(lexer, location, exp, 1, 2); 1082 case StartsWith: return checkParamCount(lexer, location, exp, 1); 1083 case EndsWith: return checkParamCount(lexer, location, exp, 1); 1084 case Matches: return checkParamCount(lexer, location, exp, 1); 1085 case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); 1086 case Contains: return checkParamCount(lexer, location, exp, 1); 1087 case Replace: return checkParamCount(lexer, location, exp, 2); 1088 case Length: return checkParamCount(lexer, location, exp, 0); 1089 case Children: return checkParamCount(lexer, location, exp, 0); 1090 case Descendants: return checkParamCount(lexer, location, exp, 0); 1091 case MemberOf: return checkParamCount(lexer, location, exp, 1); 1092 case Trace: return checkParamCount(lexer, location, exp, 1, 2); 1093 case Check: return checkParamCount(lexer, location, exp, 2); 1094 case Today: return checkParamCount(lexer, location, exp, 0); 1095 case Now: return checkParamCount(lexer, location, exp, 0); 1096 case Resolve: return checkParamCount(lexer, location, exp, 0); 1097 case Extension: return checkParamCount(lexer, location, exp, 1); 1098 case AllFalse: return checkParamCount(lexer, location, exp, 0); 1099 case AnyFalse: return checkParamCount(lexer, location, exp, 0); 1100 case AllTrue: return checkParamCount(lexer, location, exp, 0); 1101 case AnyTrue: return checkParamCount(lexer, location, exp, 0); 1102 case HasValue: return checkParamCount(lexer, location, exp, 0); 1103 case Alias: return checkParamCount(lexer, location, exp, 1); 1104 case AliasAs: return checkParamCount(lexer, location, exp, 1); 1105 case HtmlChecks: return checkParamCount(lexer, location, exp, 0); 1106 case ToInteger: return checkParamCount(lexer, location, exp, 0); 1107 case ToDecimal: return checkParamCount(lexer, location, exp, 0); 1108 case ToString: return checkParamCount(lexer, location, exp, 0); 1109 case ToQuantity: return checkParamCount(lexer, location, exp, 0); 1110 case ToBoolean: return checkParamCount(lexer, location, exp, 0); 1111 case ToDateTime: return checkParamCount(lexer, location, exp, 0); 1112 case ToTime: return checkParamCount(lexer, location, exp, 0); 1113 case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0); 1114 case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0); 1115 case ConvertsToString: return checkParamCount(lexer, location, exp, 0); 1116 case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0); 1117 case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0); 1118 case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0); 1119 case ConvertsToTime: return checkParamCount(lexer, location, exp, 0); 1120 case ConformsTo: return checkParamCount(lexer, location, exp, 1); 1121 case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 1122 } 1123 return false; 1124 } 1125 1126 private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException { 1127// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 1128 List<Base> work = new ArrayList<Base>(); 1129 switch (exp.getKind()) { 1130 case Unary: 1131 work.add(new IntegerType(0)); 1132 break; 1133 case Name: 1134 if (atEntry && exp.getName().equals("$this")) 1135 work.add(context.getThisItem()); 1136 else if (atEntry && exp.getName().equals("$total")) 1137 work.addAll(context.getTotal()); 1138 else 1139 for (Base item : focus) { 1140 List<Base> outcome = execute(context, item, exp, atEntry); 1141 for (Base base : outcome) 1142 if (base != null) 1143 work.add(base); 1144 } 1145 break; 1146 case Function: 1147 List<Base> work2 = evaluateFunction(context, focus, exp); 1148 work.addAll(work2); 1149 break; 1150 case Constant: 1151 Base b = resolveConstant(context, exp.getConstant(), false); 1152 if (b != null) 1153 work.add(b); 1154 break; 1155 case Group: 1156 work2 = execute(context, focus, exp.getGroup(), atEntry); 1157 work.addAll(work2); 1158 } 1159 1160 if (exp.getInner() != null) 1161 work = execute(context, work, exp.getInner(), false); 1162 1163 if (exp.isProximal() && exp.getOperation() != null) { 1164 ExpressionNode next = exp.getOpNext(); 1165 ExpressionNode last = exp; 1166 while (next != null) { 1167 List<Base> work2 = preOperate(work, last.getOperation()); 1168 if (work2 != null) 1169 work = work2; 1170 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 1171 work2 = executeTypeName(context, focus, next, false); 1172 work = operate(context, work, last.getOperation(), work2); 1173 } else { 1174 work2 = execute(context, focus, next, true); 1175 work = operate(context, work, last.getOperation(), work2); 1176// System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); 1177 } 1178 last = next; 1179 next = next.getOpNext(); 1180 } 1181 } 1182// System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); 1183 return work; 1184 } 1185 1186 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 1187 List<Base> result = new ArrayList<Base>(); 1188 if (next.getInner() != null) 1189 result.add(new StringType(next.getName()+"."+next.getInner().getName())); 1190 else 1191 result.add(new StringType(next.getName())); 1192 return result; 1193 } 1194 1195 1196 private List<Base> preOperate(List<Base> left, Operation operation) throws PathEngineException { 1197 if (left.size() == 0) 1198 return null; 1199 switch (operation) { 1200 case And: 1201 return isBoolean(left, false) ? makeBoolean(false) : null; 1202 case Or: 1203 return isBoolean(left, true) ? makeBoolean(true) : null; 1204 case Implies: 1205 Equality v = asBool(left); 1206 return v == Equality.False ? makeBoolean(true) : null; 1207 default: 1208 return null; 1209 } 1210 } 1211 1212 private List<Base> makeBoolean(boolean b) { 1213 List<Base> res = new ArrayList<Base>(); 1214 res.add(new BooleanType(b).noExtensions()); 1215 return res; 1216 } 1217 1218 private List<Base> makeNull() { 1219 List<Base> res = new ArrayList<Base>(); 1220 return res; 1221 } 1222 1223 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1224 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 1225 } 1226 1227 private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1228 TypeDetails result = new TypeDetails(null); 1229 switch (exp.getKind()) { 1230 case Name: 1231 if (atEntry && exp.getName().equals("$this")) 1232 result.update(context.getThisItem()); 1233 else if (atEntry && exp.getName().equals("$total")) 1234 result.update(anything(CollectionStatus.UNORDERED)); 1235 else if (atEntry && focus == null) 1236 result.update(executeContextType(context, exp.getName())); 1237 else { 1238 for (String s : focus.getTypes()) { 1239 result.update(executeType(s, exp, atEntry)); 1240 } 1241 if (result.hasNoTypes()) 1242 throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe()); 1243 } 1244 break; 1245 case Function: 1246 result.update(evaluateFunctionType(context, focus, exp)); 1247 break; 1248 case Unary: 1249 result.addType("integer"); 1250 break; 1251 case Constant: 1252 result.update(resolveConstantType(context, exp.getConstant())); 1253 break; 1254 case Group: 1255 result.update(executeType(context, focus, exp.getGroup(), atEntry)); 1256 } 1257 exp.setTypes(result); 1258 1259 if (exp.getInner() != null) { 1260 result = executeType(context, result, exp.getInner(), false); 1261 } 1262 1263 if (exp.isProximal() && exp.getOperation() != null) { 1264 ExpressionNode next = exp.getOpNext(); 1265 ExpressionNode last = exp; 1266 while (next != null) { 1267 TypeDetails work; 1268 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) 1269 work = executeTypeName(context, focus, next, atEntry); 1270 else 1271 work = executeType(context, focus, next, atEntry); 1272 result = operateTypes(result, last.getOperation(), work); 1273 last = next; 1274 next = next.getOpNext(); 1275 } 1276 exp.setOpTypes(result); 1277 } 1278 return result; 1279 } 1280 1281 private Base resolveConstant(ExecutionContext context, Base constant, boolean beforeContext) throws PathEngineException { 1282 if (!(constant instanceof FHIRConstant)) 1283 return constant; 1284 FHIRConstant c = (FHIRConstant) constant; 1285 if (c.getValue().startsWith("%")) { 1286 return resolveConstant(context, c.getValue(), beforeContext); 1287 } else if (c.getValue().startsWith("@")) { 1288 return processDateConstant(context.appInfo, c.getValue().substring(1)); 1289 } else 1290 throw new PathEngineException("Invaild FHIR Constant "+c.getValue()); 1291 } 1292 1293 private Base processDateConstant(Object appInfo, String value) throws PathEngineException { 1294 if (value.startsWith("T")) 1295 return new TimeType(value.substring(1)).noExtensions(); 1296 String v = value; 1297 if (v.length() > 10) { 1298 int i = v.substring(10).indexOf("-"); 1299 if (i == -1) 1300 i = v.substring(10).indexOf("+"); 1301 if (i == -1) 1302 i = v.substring(10).indexOf("Z"); 1303 v = i == -1 ? value : v.substring(0, 10+i); 1304 } 1305 if (v.length() > 10) 1306 return new DateTimeType(value).noExtensions(); 1307 else 1308 return new DateType(value).noExtensions(); 1309 } 1310 1311 1312 private Base resolveConstant(ExecutionContext context, String s, boolean beforeContext) throws PathEngineException { 1313 if (s.equals("%sct")) 1314 return new StringType("http://snomed.info/sct").noExtensions(); 1315 else if (s.equals("%loinc")) 1316 return new StringType("http://loinc.org").noExtensions(); 1317 else if (s.equals("%ucum")) 1318 return new StringType("http://unitsofmeasure.org").noExtensions(); 1319 else if (s.equals("%resource")) { 1320 if (context.focusResource == null) 1321 throw new PathEngineException("Cannot use %resource in this context"); 1322 return context.focusResource; 1323 } else if (s.equals("%rootResource")) { 1324 if (context.rootResource == null) 1325 throw new PathEngineException("Cannot use %rootResource in this context"); 1326 return context.rootResource; 1327 } else if (s.equals("%context")) { 1328 return context.context; 1329 } else if (s.equals("%us-zip")) 1330 return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions(); 1331 else if (s.startsWith("%`vs-")) 1332 return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions(); 1333 else if (s.startsWith("%`cs-")) 1334 return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions(); 1335 else if (s.startsWith("%`ext-")) 1336 return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions(); 1337 else if (hostServices == null) 1338 throw new PathEngineException("Unknown fixed constant '"+s+"'"); 1339 else 1340 return hostServices.resolveConstant(context.appInfo, s.substring(1), beforeContext); 1341 } 1342 1343 1344 private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException { 1345 StringBuilder b = new StringBuilder(); 1346 int i = 1; 1347 while (i < s.length()-1) { 1348 char ch = s.charAt(i); 1349 if (ch == '\\') { 1350 i++; 1351 switch (s.charAt(i)) { 1352 case 't': 1353 b.append('\t'); 1354 break; 1355 case 'r': 1356 b.append('\r'); 1357 break; 1358 case 'n': 1359 b.append('\n'); 1360 break; 1361 case 'f': 1362 b.append('\f'); 1363 break; 1364 case '\'': 1365 b.append('\''); 1366 break; 1367 case '"': 1368 b.append('"'); 1369 break; 1370 case '`': 1371 b.append('`'); 1372 break; 1373 case '\\': 1374 b.append('\\'); 1375 break; 1376 case '/': 1377 b.append('/'); 1378 break; 1379 case 'u': 1380 i++; 1381 int uc = Integer.parseInt(s.substring(i, i+4), 16); 1382 b.append((char) uc); 1383 i = i + 3; 1384 break; 1385 default: 1386 throw lexer.error("Unknown character escape \\"+s.charAt(i)); 1387 } 1388 i++; 1389 } else { 1390 b.append(ch); 1391 i++; 1392 } 1393 } 1394 return b.toString(); 1395 } 1396 1397 1398 private List<Base> operate(ExecutionContext context, List<Base> left, Operation operation, List<Base> right) throws FHIRException { 1399 switch (operation) { 1400 case Equals: return opEquals(left, right); 1401 case Equivalent: return opEquivalent(left, right); 1402 case NotEquals: return opNotEquals(left, right); 1403 case NotEquivalent: return opNotEquivalent(left, right); 1404 case LessThan: return opLessThan(left, right); 1405 case Greater: return opGreater(left, right); 1406 case LessOrEqual: return opLessOrEqual(left, right); 1407 case GreaterOrEqual: return opGreaterOrEqual(left, right); 1408 case Union: return opUnion(left, right); 1409 case In: return opIn(left, right); 1410 case MemberOf: return opMemberOf(context, left, right); 1411 case Contains: return opContains(left, right); 1412 case Or: return opOr(left, right); 1413 case And: return opAnd(left, right); 1414 case Xor: return opXor(left, right); 1415 case Implies: return opImplies(left, right); 1416 case Plus: return opPlus(left, right); 1417 case Times: return opTimes(left, right); 1418 case Minus: return opMinus(left, right); 1419 case Concatenate: return opConcatenate(left, right); 1420 case DivideBy: return opDivideBy(left, right); 1421 case Div: return opDiv(left, right); 1422 case Mod: return opMod(left, right); 1423 case Is: return opIs(left, right); 1424 case As: return opAs(left, right); 1425 default: 1426 throw new Error("Not Done Yet: "+operation.toCode()); 1427 } 1428 } 1429 1430 private List<Base> opAs(List<Base> left, List<Base> right) { 1431 List<Base> result = new ArrayList<>(); 1432 if (right.size() != 1) 1433 return result; 1434 else { 1435 String tn = convertToString(right); 1436 for (Base nextLeft : left) { 1437 if (tn.equals(nextLeft.fhirType())) 1438 result.add(nextLeft); 1439 } 1440 } 1441 return result; 1442 } 1443 1444 1445 private List<Base> opIs(List<Base> left, List<Base> right) { 1446 List<Base> result = new ArrayList<Base>(); 1447 if (left.size() != 1 || right.size() != 1) 1448 result.add(new BooleanType(false).noExtensions()); 1449 else { 1450 String tn = convertToString(right); 1451 if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element) 1452 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 1453 else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions()) 1454 result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions()); 1455 else 1456 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 1457 } 1458 return result; 1459 } 1460 1461 1462 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) { 1463 switch (operation) { 1464 case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1465 case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1466 case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1467 case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1468 case LessThan: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1469 case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1470 case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1471 case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1472 case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1473 case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 1474 case Union: return left.union(right); 1475 case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1476 case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1477 case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1478 case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1479 case Times: 1480 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 1481 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1482 result.addType(TypeDetails.FP_Integer); 1483 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1484 result.addType(TypeDetails.FP_Decimal); 1485 return result; 1486 case DivideBy: 1487 result = new TypeDetails(CollectionStatus.SINGLETON); 1488 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1489 result.addType(TypeDetails.FP_Decimal); 1490 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1491 result.addType(TypeDetails.FP_Decimal); 1492 return result; 1493 case Concatenate: 1494 result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 1495 return result; 1496 case Plus: 1497 result = new TypeDetails(CollectionStatus.SINGLETON); 1498 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1499 result.addType(TypeDetails.FP_Integer); 1500 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1501 result.addType(TypeDetails.FP_Decimal); 1502 else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) 1503 result.addType(TypeDetails.FP_String); 1504 return result; 1505 case Minus: 1506 result = new TypeDetails(CollectionStatus.SINGLETON); 1507 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1508 result.addType(TypeDetails.FP_Integer); 1509 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1510 result.addType(TypeDetails.FP_Decimal); 1511 return result; 1512 case Div: 1513 case Mod: 1514 result = new TypeDetails(CollectionStatus.SINGLETON); 1515 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1516 result.addType(TypeDetails.FP_Integer); 1517 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1518 result.addType(TypeDetails.FP_Decimal); 1519 return result; 1520 case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1521 case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1522 case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1523 default: 1524 return null; 1525 } 1526 } 1527 1528 1529 private List<Base> opEquals(List<Base> left, List<Base> right) { 1530 if (left.size() == 0 || right.size() == 0) 1531 return new ArrayList<Base>(); 1532 1533 if (left.size() != right.size()) 1534 return makeBoolean(false); 1535 1536 boolean res = true; 1537 boolean nil = false; 1538 for (int i = 0; i < left.size(); i++) { 1539 Boolean eq = doEquals(left.get(i), right.get(i)); 1540 if (eq == null) 1541 nil = true; 1542 else if (eq == false) { 1543 res = false; 1544 break; 1545 } 1546 } 1547 if (!res) 1548 return makeBoolean(res); 1549 else if (nil) 1550 return new ArrayList<Base>(); 1551 else 1552 return makeBoolean(res); 1553 } 1554 1555 private List<Base> opNotEquals(List<Base> left, List<Base> right) { 1556 if (!legacyMode && (left.size() == 0 || right.size() == 0)) 1557 return new ArrayList<Base>(); 1558 1559 if (left.size() != right.size()) 1560 return makeBoolean(true); 1561 1562 boolean res = true; 1563 boolean nil = false; 1564 for (int i = 0; i < left.size(); i++) { 1565 Boolean eq = doEquals(left.get(i), right.get(i)); 1566 if (eq == null) 1567 nil = true; 1568 else if (eq == true) { 1569 res = false; 1570 break; 1571 } 1572 } 1573 if (!res) 1574 return makeBoolean(res); 1575 else if (nil) 1576 return new ArrayList<Base>(); 1577 else 1578 return makeBoolean(res); 1579 } 1580 1581 private String removeTrailingZeros(String s) { 1582 if (Utilities.noString(s)) 1583 return ""; 1584 int i = s.length()-1; 1585 boolean done = false; 1586 boolean dot = false; 1587 while (i > 0 && !done) { 1588 if (s.charAt(i) == '.') { 1589 i--; 1590 dot = true; 1591 } 1592 else if (!dot && s.charAt(i) == '0') 1593 i--; 1594 else 1595 done = true; 1596 } 1597 return s.substring(0, i+1); 1598 } 1599 1600 private boolean decEqual(String left, String right) { 1601 left = removeTrailingZeros(left); 1602 right = removeTrailingZeros(right); 1603 return left.equals(right); 1604 } 1605 1606 private Boolean compareDates(BaseDateTimeType left, BaseDateTimeType right) { 1607 return left.equalsUsingFhirPathRules(right); 1608 } 1609 1610 private Boolean doEquals(Base left, Base right) { 1611 if (left instanceof Quantity && right instanceof Quantity) 1612 return qtyEqual((Quantity) left, (Quantity) right); 1613 else if (left.isDateTime() && right.isDateTime()) { 1614 return compareDates(left.dateTimeValue(), right.dateTimeValue()); 1615 } else if (left instanceof DecimalType || right instanceof DecimalType) 1616 return decEqual(left.primitiveValue(), right.primitiveValue()); 1617 else if (left.isPrimitive() && right.isPrimitive()) 1618 return Base.equals(left.primitiveValue(), right.primitiveValue()); 1619 else 1620 return Base.compareDeep(left, right, false); 1621 } 1622 1623 1624 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 1625 if (left instanceof Quantity && right instanceof Quantity) 1626 return qtyEquivalent((Quantity) left, (Quantity) right); 1627 if (left.hasType("integer") && right.hasType("integer")) 1628 return doEquals(left, right); 1629 if (left.hasType("boolean") && right.hasType("boolean")) 1630 return doEquals(left, right); 1631 if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) 1632 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1633 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) 1634 return compareDateTimeElements(left, right, true) == 0; 1635 if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) 1636 return Utilities.equivalent(convertToString(left), convertToString(right)); 1637 1638 throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType())); 1639 } 1640 1641 private boolean qtyEqual(Quantity left, Quantity right) { 1642 if (worker.getUcumService() != null) { 1643 DecimalType dl = qtyToCanonical(left); 1644 DecimalType dr = qtyToCanonical(right); 1645 if (dl != null && dr != null) 1646 return doEquals(dl, dr); 1647 } 1648 return left.equals(right); 1649 } 1650 1651 private DecimalType qtyToCanonical(Quantity q) { 1652 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 1653 return null; 1654 try { 1655 Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 1656 Pair c = worker.getUcumService().getCanonicalForm(p); 1657 return new DecimalType(c.getValue().asDecimal()); 1658 } catch (UcumException e) { 1659 return null; 1660 } 1661 } 1662 1663 private Base pairToQty(Pair p) { 1664 return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions(); 1665 } 1666 1667 1668 private Pair qtyToPair(Quantity q) { 1669 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 1670 return null; 1671 try { 1672 return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 1673 } catch (UcumException e) { 1674 return null; 1675 } 1676 } 1677 1678 1679 private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { 1680 if (worker.getUcumService() != null) { 1681 DecimalType dl = qtyToCanonical(left); 1682 DecimalType dr = qtyToCanonical(right); 1683 if (dl != null && dr != null) 1684 return doEquivalent(dl, dr); 1685 } 1686 return left.equals(right); 1687 } 1688 1689 1690 1691 private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1692 if (left.size() != right.size()) 1693 return makeBoolean(false); 1694 1695 boolean res = true; 1696 for (int i = 0; i < left.size(); i++) { 1697 boolean found = false; 1698 for (int j = 0; j < right.size(); j++) { 1699 if (doEquivalent(left.get(i), right.get(j))) { 1700 found = true; 1701 break; 1702 } 1703 } 1704 if (!found) { 1705 res = false; 1706 break; 1707 } 1708 } 1709 return makeBoolean(res); 1710 } 1711 1712 private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1713 if (left.size() != right.size()) 1714 return makeBoolean(true); 1715 1716 boolean res = true; 1717 for (int i = 0; i < left.size(); i++) { 1718 boolean found = false; 1719 for (int j = 0; j < right.size(); j++) { 1720 if (doEquivalent(left.get(i), right.get(j))) { 1721 found = true; 1722 break; 1723 } 1724 } 1725 if (!found) { 1726 res = false; 1727 break; 1728 } 1729 } 1730 return makeBoolean(!res); 1731 } 1732 1733 private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url"}; 1734 1735 private List<Base> opLessThan(List<Base> left, List<Base> right) throws FHIRException { 1736 if (left.size() == 0 || right.size() == 0) 1737 return new ArrayList<Base>(); 1738 1739 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1740 Base l = left.get(0); 1741 Base r = right.get(0); 1742 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1743 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1744 else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 1745 return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); 1746 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1747 return makeBoolean(compareDateTimeElements(l, r, false) < 0); 1748 else if ((l.hasType("time")) && (r.hasType("time"))) 1749 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1750 else 1751 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1752 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1753 List<Base> lUnit = left.get(0).listChildrenByName("code"); 1754 List<Base> rUnit = right.get(0).listChildrenByName("code"); 1755 if (Base.compareDeep(lUnit, rUnit, true)) { 1756 return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1757 } else { 1758 if (worker.getUcumService() == null) 1759 return makeBoolean(false); 1760 else { 1761 List<Base> dl = new ArrayList<Base>(); 1762 dl.add(qtyToCanonical((Quantity) left.get(0))); 1763 List<Base> dr = new ArrayList<Base>(); 1764 dr.add(qtyToCanonical((Quantity) right.get(0))); 1765 return opLessThan(dl, dr); 1766 } 1767 } 1768 } 1769 return new ArrayList<Base>(); 1770 } 1771 1772 private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException { 1773 if (left.size() == 0 || right.size() == 0) 1774 return new ArrayList<Base>(); 1775 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1776 Base l = left.get(0); 1777 Base r = right.get(0); 1778 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1779 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1780 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1781 return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); 1782 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1783 return makeBoolean(compareDateTimeElements(l, r, false) > 0); 1784 else if ((l.hasType("time")) && (r.hasType("time"))) 1785 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1786 else 1787 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1788 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1789 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1790 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1791 if (Base.compareDeep(lUnit, rUnit, true)) { 1792 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1793 } else { 1794 if (worker.getUcumService() == null) 1795 return makeBoolean(false); 1796 else { 1797 List<Base> dl = new ArrayList<Base>(); 1798 dl.add(qtyToCanonical((Quantity) left.get(0))); 1799 List<Base> dr = new ArrayList<Base>(); 1800 dr.add(qtyToCanonical((Quantity) right.get(0))); 1801 return opGreater(dl, dr); 1802 } 1803 } 1804 } 1805 return new ArrayList<Base>(); 1806 } 1807 1808 private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1809 if (left.size() == 0 || right.size() == 0) 1810 return new ArrayList<Base>(); 1811 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1812 Base l = left.get(0); 1813 Base r = right.get(0); 1814 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1815 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1816 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1817 return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); 1818 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1819 return makeBoolean(compareDateTimeElements(l, r, false) <= 0); 1820 else if ((l.hasType("time")) && (r.hasType("time"))) 1821 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1822 else 1823 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1824 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1825 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 1826 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 1827 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 1828 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 1829 if ((lunit == null && runit == null) || lunit.equals(runit)) { 1830 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1831 } else { 1832 if (worker.getUcumService() == null) 1833 return makeBoolean(false); 1834 else { 1835 List<Base> dl = new ArrayList<Base>(); 1836 dl.add(qtyToCanonical((Quantity) left.get(0))); 1837 List<Base> dr = new ArrayList<Base>(); 1838 dr.add(qtyToCanonical((Quantity) right.get(0))); 1839 return opLessOrEqual(dl, dr); 1840 } 1841 } 1842 } 1843 return new ArrayList<Base>(); 1844 } 1845 1846 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1847 if (left.size() == 0 || right.size() == 0) 1848 return new ArrayList<Base>(); 1849 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1850 Base l = left.get(0); 1851 Base r = right.get(0); 1852 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1853 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1854 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1855 return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); 1856 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1857 return makeBoolean(compareDateTimeElements(l, r, false) >= 0); 1858 else if ((l.hasType("time")) && (r.hasType("time"))) 1859 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1860 else 1861 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1862 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1863 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1864 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1865 if (Base.compareDeep(lUnit, rUnit, true)) { 1866 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1867 } else { 1868 if (worker.getUcumService() == null) 1869 return makeBoolean(false); 1870 else { 1871 List<Base> dl = new ArrayList<Base>(); 1872 dl.add(qtyToCanonical((Quantity) left.get(0))); 1873 List<Base> dr = new ArrayList<Base>(); 1874 dr.add(qtyToCanonical((Quantity) right.get(0))); 1875 return opGreaterOrEqual(dl, dr); 1876 } 1877 } 1878 } 1879 return new ArrayList<Base>(); 1880 } 1881 1882 private List<Base> opMemberOf(ExecutionContext context, List<Base> left, List<Base> right) throws FHIRException { 1883 boolean ans = false; 1884 ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, right.get(0).primitiveValue()) : worker.fetchResource(ValueSet.class, right.get(0).primitiveValue()); 1885 if (vs != null) { 1886 for (Base l : left) { 1887 if (l.fhirType().equals("code")) { 1888 if (worker.validateCode(terminologyServiceOptions , l.castToCoding(l), vs).isOk()) 1889 ans = true; 1890 } else if (l.fhirType().equals("Coding")) { 1891 if (worker.validateCode(terminologyServiceOptions, l.castToCoding(l), vs).isOk()) 1892 ans = true; 1893 } else if (l.fhirType().equals("CodeableConcept")) { 1894 if (worker.validateCode(terminologyServiceOptions, l.castToCodeableConcept(l), vs).isOk()) 1895 ans = true; 1896 } 1897 } 1898 } 1899 return makeBoolean(ans); 1900 } 1901 1902 private List<Base> opIn(List<Base> left, List<Base> right) throws FHIRException { 1903 if (left.size() == 0) 1904 return new ArrayList<Base>(); 1905 if (right.size() == 0) 1906 return makeBoolean(false); 1907 boolean ans = true; 1908 for (Base l : left) { 1909 boolean f = false; 1910 for (Base r : right) { 1911 Boolean eq = doEquals(l, r); 1912 if (eq != null && eq == true) { 1913 f = true; 1914 break; 1915 } 1916 } 1917 if (!f) { 1918 ans = false; 1919 break; 1920 } 1921 } 1922 return makeBoolean(ans); 1923 } 1924 1925 private List<Base> opContains(List<Base> left, List<Base> right) { 1926 if (left.size() == 0 || right.size() == 0) 1927 return new ArrayList<Base>(); 1928 boolean ans = true; 1929 for (Base r : right) { 1930 boolean f = false; 1931 for (Base l : left) { 1932 Boolean eq = doEquals(l, r); 1933 if (eq != null && eq == true) { 1934 f = true; 1935 break; 1936 } 1937 } 1938 if (!f) { 1939 ans = false; 1940 break; 1941 } 1942 } 1943 return makeBoolean(ans); 1944 } 1945 1946 private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException { 1947 if (left.size() == 0 || right.size() == 0) 1948 return new ArrayList<Base>(); 1949 if (left.size() > 1) 1950 throw new PathEngineException("Error performing +: left operand has more than one value"); 1951 if (!left.get(0).isPrimitive()) 1952 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1953 if (right.size() > 1) 1954 throw new PathEngineException("Error performing +: right operand has more than one value"); 1955 if (!right.get(0).isPrimitive()) 1956 throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType())); 1957 1958 List<Base> result = new ArrayList<Base>(); 1959 Base l = left.get(0); 1960 Base r = right.get(0); 1961 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1962 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 1963 else if (l.hasType("integer") && r.hasType("integer")) 1964 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 1965 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1966 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 1967 else 1968 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())); 1969 return result; 1970 } 1971 1972 private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException { 1973 if (left.size() == 0 || right.size() == 0) 1974 return new ArrayList<Base>(); 1975 if (left.size() > 1) 1976 throw new PathEngineException("Error performing *: left operand has more than one value"); 1977 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 1978 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1979 if (right.size() > 1) 1980 throw new PathEngineException("Error performing *: right operand has more than one value"); 1981 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 1982 throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType())); 1983 1984 List<Base> result = new ArrayList<Base>(); 1985 Base l = left.get(0); 1986 Base r = right.get(0); 1987 1988 if (l.hasType("integer") && r.hasType("integer")) 1989 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 1990 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1991 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 1992 else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 1993 Pair pl = qtyToPair((Quantity) l); 1994 Pair pr = qtyToPair((Quantity) r); 1995 Pair p; 1996 try { 1997 p = worker.getUcumService().multiply(pl, pr); 1998 result.add(pairToQty(p)); 1999 } catch (UcumException e) { 2000 throw new PathEngineException(e.getMessage(), e); 2001 } 2002 } else 2003 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())); 2004 return result; 2005 } 2006 2007 2008 private List<Base> opConcatenate(List<Base> left, List<Base> right) throws PathEngineException { 2009 if (left.size() > 1) 2010 throw new PathEngineException("Error performing &: left operand has more than one value"); 2011 if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) 2012 throw new PathEngineException(String.format("Error performing &: left operand has the wrong type (%s)", left.get(0).fhirType())); 2013 if (right.size() > 1) 2014 throw new PathEngineException("Error performing &: right operand has more than one value"); 2015 if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) 2016 throw new PathEngineException(String.format("Error performing &: right operand has the wrong type (%s)", right.get(0).fhirType())); 2017 2018 List<Base> result = new ArrayList<Base>(); 2019 String l = left.size() == 0 ? "" : left.get(0).primitiveValue(); 2020 String r = right.size() == 0 ? "" : right.get(0).primitiveValue(); 2021 result.add(new StringType(l + r)); 2022 return result; 2023 } 2024 2025 private List<Base> opUnion(List<Base> left, List<Base> right) { 2026 List<Base> result = new ArrayList<Base>(); 2027 for (Base item : left) { 2028 if (!doContains(result, item)) 2029 result.add(item); 2030 } 2031 for (Base item : right) { 2032 if (!doContains(result, item)) 2033 result.add(item); 2034 } 2035 return result; 2036 } 2037 2038 private boolean doContains(List<Base> list, Base item) { 2039 for (Base test : list) { 2040 Boolean eq = doEquals(test, item); 2041 if (eq != null && eq == true) 2042 return true; 2043 } 2044 return false; 2045 } 2046 2047 2048 private List<Base> opAnd(List<Base> left, List<Base> right) throws PathEngineException { 2049 Equality l = asBool(left); 2050 Equality r = asBool(right); 2051 switch (l) { 2052 case False: return makeBoolean(false); 2053 case Null: 2054 if (r == Equality.False) 2055 return makeBoolean(false); 2056 else 2057 return makeNull(); 2058 case True: 2059 switch (r) { 2060 case False: return makeBoolean(false); 2061 case Null: return makeNull(); 2062 case True: return makeBoolean(true); 2063 } 2064 } 2065 return makeNull(); 2066 } 2067 2068 private boolean isBoolean(List<Base> list, boolean b) { 2069 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 2070 } 2071 2072 private List<Base> opOr(List<Base> left, List<Base> right) throws PathEngineException { 2073 Equality l = asBool(left); 2074 Equality r = asBool(right); 2075 switch (l) { 2076 case True: return makeBoolean(true); 2077 case Null: 2078 if (r == Equality.True) 2079 return makeBoolean(true); 2080 else 2081 return makeNull(); 2082 case False: 2083 switch (r) { 2084 case False: return makeBoolean(false); 2085 case Null: return makeNull(); 2086 case True: return makeBoolean(true); 2087 } 2088 } 2089 return makeNull(); 2090 } 2091 2092 private List<Base> opXor(List<Base> left, List<Base> right) throws PathEngineException { 2093 Equality l = asBool(left); 2094 Equality r = asBool(right); 2095 switch (l) { 2096 case True: 2097 switch (r) { 2098 case False: return makeBoolean(true); 2099 case True: return makeBoolean(false); 2100 case Null: return makeNull(); 2101 } 2102 case Null: 2103 return makeNull(); 2104 case False: 2105 switch (r) { 2106 case False: return makeBoolean(false); 2107 case True: return makeBoolean(true); 2108 case Null: return makeNull(); 2109 } 2110 } 2111 return makeNull(); 2112 } 2113 2114 private List<Base> opImplies(List<Base> left, List<Base> right) throws PathEngineException { 2115 Equality eq = asBool(left); 2116 if (eq == Equality.False) 2117 return makeBoolean(true); 2118 else if (right.size() == 0) 2119 return makeNull(); 2120 else switch (asBool(right)) { 2121 case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); 2122 case Null: return makeNull(); 2123 case True: return makeBoolean(true); 2124 } 2125 return makeNull(); 2126 } 2127 2128 2129 private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException { 2130 if (left.size() == 0 || right.size() == 0) 2131 return new ArrayList<Base>(); 2132 if (left.size() > 1) 2133 throw new PathEngineException("Error performing -: left operand has more than one value"); 2134 if (!left.get(0).isPrimitive()) 2135 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2136 if (right.size() > 1) 2137 throw new PathEngineException("Error performing -: right operand has more than one value"); 2138 if (!right.get(0).isPrimitive()) 2139 throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType())); 2140 2141 List<Base> result = new ArrayList<Base>(); 2142 Base l = left.get(0); 2143 Base r = right.get(0); 2144 2145 if (l.hasType("integer") && r.hasType("integer")) 2146 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 2147 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 2148 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 2149 else 2150 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())); 2151 return result; 2152 } 2153 2154 private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException { 2155 if (left.size() == 0 || right.size() == 0) 2156 return new ArrayList<Base>(); 2157 if (left.size() > 1) 2158 throw new PathEngineException("Error performing /: left operand has more than one value"); 2159 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2160 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2161 if (right.size() > 1) 2162 throw new PathEngineException("Error performing /: right operand has more than one value"); 2163 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2164 throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType())); 2165 2166 List<Base> result = new ArrayList<Base>(); 2167 Base l = left.get(0); 2168 Base r = right.get(0); 2169 2170 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 2171 Decimal d1; 2172 try { 2173 d1 = new Decimal(l.primitiveValue()); 2174 Decimal d2 = new Decimal(r.primitiveValue()); 2175 result.add(new DecimalType(d1.divide(d2).asDecimal())); 2176 } catch (UcumException e) { 2177 throw new PathEngineException(e); 2178 } 2179 } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2180 Pair pl = qtyToPair((Quantity) l); 2181 Pair pr = qtyToPair((Quantity) r); 2182 Pair p; 2183 try { 2184 p = worker.getUcumService().multiply(pl, pr); 2185 result.add(pairToQty(p)); 2186 } catch (UcumException e) { 2187 throw new PathEngineException(e.getMessage(), e); 2188 } 2189 } else 2190 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())); 2191 return result; 2192 } 2193 2194 private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException { 2195 if (left.size() == 0 || right.size() == 0) 2196 return new ArrayList<Base>(); 2197 if (left.size() > 1) 2198 throw new PathEngineException("Error performing div: left operand has more than one value"); 2199 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2200 throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType())); 2201 if (right.size() > 1) 2202 throw new PathEngineException("Error performing div: right operand has more than one value"); 2203 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2204 throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType())); 2205 2206 List<Base> result = new ArrayList<Base>(); 2207 Base l = left.get(0); 2208 Base r = right.get(0); 2209 2210 if (l.hasType("integer") && r.hasType("integer")) 2211 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue()))); 2212 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2213 Decimal d1; 2214 try { 2215 d1 = new Decimal(l.primitiveValue()); 2216 Decimal d2 = new Decimal(r.primitiveValue()); 2217 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 2218 } catch (UcumException e) { 2219 throw new PathEngineException(e); 2220 } 2221 } 2222 else 2223 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())); 2224 return result; 2225 } 2226 2227 private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException { 2228 if (left.size() == 0 || right.size() == 0) 2229 return new ArrayList<Base>(); 2230 if (left.size() > 1) 2231 throw new PathEngineException("Error performing mod: left operand has more than one value"); 2232 if (!left.get(0).isPrimitive()) 2233 throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType())); 2234 if (right.size() > 1) 2235 throw new PathEngineException("Error performing mod: right operand has more than one value"); 2236 if (!right.get(0).isPrimitive()) 2237 throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType())); 2238 2239 List<Base> result = new ArrayList<Base>(); 2240 Base l = left.get(0); 2241 Base r = right.get(0); 2242 2243 if (l.hasType("integer") && r.hasType("integer")) 2244 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue()))); 2245 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2246 Decimal d1; 2247 try { 2248 d1 = new Decimal(l.primitiveValue()); 2249 Decimal d2 = new Decimal(r.primitiveValue()); 2250 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 2251 } catch (UcumException e) { 2252 throw new PathEngineException(e); 2253 } 2254 } 2255 else 2256 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())); 2257 return result; 2258 } 2259 2260 2261 private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant) throws PathEngineException { 2262 if (constant instanceof BooleanType) 2263 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2264 else if (constant instanceof IntegerType) 2265 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2266 else if (constant instanceof DecimalType) 2267 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 2268 else if (constant instanceof Quantity) 2269 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 2270 else if (constant instanceof FHIRConstant) 2271 return resolveConstantType(context, ((FHIRConstant) constant).getValue()); 2272 else 2273 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2274 } 2275 2276 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException { 2277 if (s.startsWith("@")) { 2278 if (s.startsWith("@T")) 2279 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 2280 else 2281 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2282 } else if (s.equals("%sct")) 2283 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2284 else if (s.equals("%loinc")) 2285 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2286 else if (s.equals("%ucum")) 2287 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2288 else if (s.equals("%resource")) { 2289 if (context.resource == null) 2290 throw new PathEngineException("%resource cannot be used in this context"); 2291 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 2292 } else if (s.equals("%rootResource")) { 2293 if (context.resource == null) 2294 throw new PathEngineException("%rootResource cannot be used in this context"); 2295 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 2296 } else if (s.equals("%context")) { 2297 return context.context; 2298 } else if (s.equals("%map-codes")) 2299 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2300 else if (s.equals("%us-zip")) 2301 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2302 else if (s.startsWith("%`vs-")) 2303 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2304 else if (s.startsWith("%`cs-")) 2305 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2306 else if (s.startsWith("%`ext-")) 2307 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2308 else if (hostServices == null) 2309 throw new PathEngineException("Unknown fixed constant type for '"+s+"'"); 2310 else 2311 return hostServices.resolveConstantType(context.appInfo, s); 2312 } 2313 2314 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 2315 List<Base> result = new ArrayList<Base>(); 2316 if (atEntry && context.appInfo != null && hostServices != null) { 2317 // we'll see if the name matches a constant known by the context. 2318 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true); 2319 if (temp != null) { 2320 result.add(temp); 2321 return result; 2322 } 2323 } 2324 if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 2325 if (item.isResource() && item.fhirType().equals(exp.getName())) 2326 result.add(item); 2327 } else 2328 getChildrenByName(item, exp.getName(), result); 2329 if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) { 2330 // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. 2331 // (if the name does match, and the user wants to get the constant value, they'll have to try harder... 2332 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false); 2333 if (temp != null) { 2334 result.add(temp); 2335 } 2336 } 2337 return result; 2338 } 2339 2340 private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException { 2341 if (hostServices == null) 2342 throw new PathEngineException("Unable to resolve context reference since no host services are provided"); 2343 return hostServices.resolveConstantType(context.appInfo, name); 2344 } 2345 2346 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 2347 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up 2348 return new TypeDetails(CollectionStatus.SINGLETON, type); 2349 TypeDetails result = new TypeDetails(null); 2350 getChildTypesByName(type, exp.getName(), result); 2351 return result; 2352 } 2353 2354 2355 private String hashTail(String type) { 2356 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); 2357 } 2358 2359 2360 @SuppressWarnings("unchecked") 2361 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { 2362 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 2363 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) 2364 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2365 else 2366 for (ExpressionNode expr : exp.getParameters()) { 2367 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) 2368 paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); 2369 else 2370 paramTypes.add(executeType(context, focus, expr, true)); 2371 } 2372 switch (exp.getFunction()) { 2373 case Empty : 2374 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2375 case Not : 2376 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2377 case Exists : 2378 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2379 case SubsetOf : { 2380 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 2381 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2382 } 2383 case SupersetOf : { 2384 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 2385 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2386 } 2387 case IsDistinct : 2388 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2389 case Distinct : 2390 return focus; 2391 case Count : 2392 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2393 case Where : 2394 return focus; 2395 case Select : 2396 return anything(focus.getCollectionStatus()); 2397 case All : 2398 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2399 case Repeat : 2400 return anything(focus.getCollectionStatus()); 2401 case Aggregate : 2402 return anything(focus.getCollectionStatus()); 2403 case Item : { 2404 checkOrdered(focus, "item"); 2405 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2406 return focus; 2407 } 2408 case As : { 2409 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2410 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 2411 } 2412 case OfType : { 2413 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2414 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 2415 } 2416 case Type : { 2417 boolean s = false; 2418 boolean c = false; 2419 for (ProfiledType pt : focus.getProfiledTypes()) { 2420 s = s || pt.isSystemType(); 2421 c = c || !pt.isSystemType(); 2422 } 2423 if (s && c) 2424 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); 2425 else if (s) 2426 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); 2427 else 2428 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); 2429 } 2430 case Is : { 2431 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2432 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2433 } 2434 case Single : 2435 return focus.toSingleton(); 2436 case First : { 2437 checkOrdered(focus, "first"); 2438 return focus.toSingleton(); 2439 } 2440 case Last : { 2441 checkOrdered(focus, "last"); 2442 return focus.toSingleton(); 2443 } 2444 case Tail : { 2445 checkOrdered(focus, "tail"); 2446 return focus; 2447 } 2448 case Skip : { 2449 checkOrdered(focus, "skip"); 2450 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2451 return focus; 2452 } 2453 case Take : { 2454 checkOrdered(focus, "take"); 2455 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2456 return focus; 2457 } 2458 case Union : { 2459 return focus.union(paramTypes.get(0)); 2460 } 2461 case Combine : { 2462 return focus.union(paramTypes.get(0)); 2463 } 2464 case Intersect : { 2465 return focus.intersect(paramTypes.get(0)); 2466 } 2467 case Exclude : { 2468 return focus; 2469 } 2470 case Iif : { 2471 TypeDetails types = new TypeDetails(null); 2472 types.update(paramTypes.get(0)); 2473 if (paramTypes.size() > 1) 2474 types.update(paramTypes.get(1)); 2475 return types; 2476 } 2477 case Lower : { 2478 checkContextString(focus, "lower"); 2479 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2480 } 2481 case Upper : { 2482 checkContextString(focus, "upper"); 2483 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2484 } 2485 case ToChars : { 2486 checkContextString(focus, "toChars"); 2487 return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 2488 } 2489 case IndexOf : { 2490 checkContextString(focus, "indexOf"); 2491 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2492 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2493 } 2494 case Substring : { 2495 checkContextString(focus, "subString"); 2496 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2497 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2498 } 2499 case StartsWith : { 2500 checkContextString(focus, "startsWith"); 2501 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2502 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2503 } 2504 case EndsWith : { 2505 checkContextString(focus, "endsWith"); 2506 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2507 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2508 } 2509 case Matches : { 2510 checkContextString(focus, "matches"); 2511 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2512 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2513 } 2514 case ReplaceMatches : { 2515 checkContextString(focus, "replaceMatches"); 2516 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2517 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2518 } 2519 case Contains : { 2520 checkContextString(focus, "contains"); 2521 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2522 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2523 } 2524 case Replace : { 2525 checkContextString(focus, "replace"); 2526 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2527 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2528 } 2529 case Length : { 2530 checkContextPrimitive(focus, "length", false); 2531 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2532 } 2533 case Children : 2534 return childTypes(focus, "*"); 2535 case Descendants : 2536 return childTypes(focus, "**"); 2537 case MemberOf : { 2538 checkContextCoded(focus, "memberOf"); 2539 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2540 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2541 } 2542 case Trace : { 2543 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2544 return focus; 2545 } 2546 case Check : { 2547 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2548 return focus; 2549 } 2550 case Today : 2551 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2552 case Now : 2553 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2554 case Resolve : { 2555 checkContextReference(focus, "resolve"); 2556 return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 2557 } 2558 case Extension : { 2559 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2560 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 2561 } 2562 case AnyTrue: 2563 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2564 case AllTrue: 2565 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2566 case AnyFalse: 2567 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2568 case AllFalse: 2569 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2570 case HasValue : 2571 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2572 case HtmlChecks : 2573 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2574 case Alias : 2575 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2576 return anything(CollectionStatus.SINGLETON); 2577 case AliasAs : 2578 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2579 return focus; 2580 case ToInteger : { 2581 checkContextPrimitive(focus, "toInteger", true); 2582 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2583 } 2584 case ToDecimal : { 2585 checkContextPrimitive(focus, "toDecimal", true); 2586 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 2587 } 2588 case ToString : { 2589 checkContextPrimitive(focus, "toString", true); 2590 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2591 } 2592 case ToQuantity : { 2593 checkContextPrimitive(focus, "toQuantity", true); 2594 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 2595 } 2596 case ToBoolean : { 2597 checkContextPrimitive(focus, "toBoolean", false); 2598 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2599 } 2600 case ToDateTime : { 2601 checkContextPrimitive(focus, "toBoolean", false); 2602 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2603 } 2604 case ToTime : { 2605 checkContextPrimitive(focus, "toBoolean", false); 2606 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 2607 } 2608 case ConvertsToString : 2609 case ConvertsToQuantity :{ 2610 checkContextPrimitive(focus, exp.getFunction().toCode(), true); 2611 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2612 } 2613 case ConvertsToInteger : 2614 case ConvertsToDecimal : 2615 case ConvertsToDateTime : 2616 case ConvertsToTime : 2617 case ConvertsToBoolean : { 2618 checkContextPrimitive(focus, exp.getFunction().toCode(), false); 2619 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2620 } 2621 case ConformsTo: { 2622 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2623 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2624 } 2625 case Custom : { 2626 return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); 2627 } 2628 default: 2629 break; 2630 } 2631 throw new Error("not Implemented yet"); 2632 } 2633 2634 2635 private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 2636 int i = 0; 2637 for (TypeDetails pt : typeSet) { 2638 if (i == paramTypes.size()) 2639 return; 2640 TypeDetails actual = paramTypes.get(i); 2641 i++; 2642 for (String a : actual.getTypes()) { 2643 if (!pt.hasType(worker, a)) 2644 throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 2645 } 2646 } 2647 } 2648 2649 private void checkOrdered(TypeDetails focus, String name) throws PathEngineException { 2650 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) 2651 throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 2652 } 2653 2654 private void checkContextReference(TypeDetails focus, String name) throws PathEngineException { 2655 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) 2656 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, canonical, Reference"); 2657 } 2658 2659 2660 private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException { 2661 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) 2662 throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept"); 2663 } 2664 2665 2666 private void checkContextString(TypeDetails focus, String name) throws PathEngineException { 2667 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) 2668 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 2669 } 2670 2671 2672 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty) throws PathEngineException { 2673 if (canQty) { 2674 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) 2675 throw new PathEngineException("The function '"+name+"'() can only be used on a Quantity or on "+primitiveTypes.toString()); 2676 } else if (!focus.hasType(primitiveTypes)) 2677 throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()); 2678 } 2679 2680 2681 private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException { 2682 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 2683 for (String f : focus.getTypes()) 2684 getChildTypesByName(f, mask, result); 2685 return result; 2686 } 2687 2688 private TypeDetails anything(CollectionStatus status) { 2689 return new TypeDetails(status, allTypes.keySet()); 2690 } 2691 2692 // private boolean isPrimitiveType(String s) { 2693 // 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"); 2694 // } 2695 2696 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2697 switch (exp.getFunction()) { 2698 case Empty : return funcEmpty(context, focus, exp); 2699 case Not : return funcNot(context, focus, exp); 2700 case Exists : return funcExists(context, focus, exp); 2701 case SubsetOf : return funcSubsetOf(context, focus, exp); 2702 case SupersetOf : return funcSupersetOf(context, focus, exp); 2703 case IsDistinct : return funcIsDistinct(context, focus, exp); 2704 case Distinct : return funcDistinct(context, focus, exp); 2705 case Count : return funcCount(context, focus, exp); 2706 case Where : return funcWhere(context, focus, exp); 2707 case Select : return funcSelect(context, focus, exp); 2708 case All : return funcAll(context, focus, exp); 2709 case Repeat : return funcRepeat(context, focus, exp); 2710 case Aggregate : return funcAggregate(context, focus, exp); 2711 case Item : return funcItem(context, focus, exp); 2712 case As : return funcAs(context, focus, exp); 2713 case OfType : return funcAs(context, focus, exp); 2714 case Type : return funcType(context, focus, exp); 2715 case Is : return funcIs(context, focus, exp); 2716 case Single : return funcSingle(context, focus, exp); 2717 case First : return funcFirst(context, focus, exp); 2718 case Last : return funcLast(context, focus, exp); 2719 case Tail : return funcTail(context, focus, exp); 2720 case Skip : return funcSkip(context, focus, exp); 2721 case Take : return funcTake(context, focus, exp); 2722 case Union : return funcUnion(context, focus, exp); 2723 case Combine : return funcCombine(context, focus, exp); 2724 case Intersect : return funcIntersect(context, focus, exp); 2725 case Exclude : return funcExclude(context, focus, exp); 2726 case Iif : return funcIif(context, focus, exp); 2727 case Lower : return funcLower(context, focus, exp); 2728 case Upper : return funcUpper(context, focus, exp); 2729 case ToChars : return funcToChars(context, focus, exp); 2730 case IndexOf : return funcIndexOf(context, focus, exp); 2731 case Substring : return funcSubstring(context, focus, exp); 2732 case StartsWith : return funcStartsWith(context, focus, exp); 2733 case EndsWith : return funcEndsWith(context, focus, exp); 2734 case Matches : return funcMatches(context, focus, exp); 2735 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 2736 case Contains : return funcContains(context, focus, exp); 2737 case Replace : return funcReplace(context, focus, exp); 2738 case Length : return funcLength(context, focus, exp); 2739 case Children : return funcChildren(context, focus, exp); 2740 case Descendants : return funcDescendants(context, focus, exp); 2741 case MemberOf : return funcMemberOf(context, focus, exp); 2742 case Trace : return funcTrace(context, focus, exp); 2743 case Check : return funcCheck(context, focus, exp); 2744 case Today : return funcToday(context, focus, exp); 2745 case Now : return funcNow(context, focus, exp); 2746 case Resolve : return funcResolve(context, focus, exp); 2747 case Extension : return funcExtension(context, focus, exp); 2748 case AnyFalse: return funcAnyFalse(context, focus, exp); 2749 case AllFalse: return funcAllFalse(context, focus, exp); 2750 case AnyTrue: return funcAnyTrue(context, focus, exp); 2751 case AllTrue: return funcAllTrue(context, focus, exp); 2752 case HasValue : return funcHasValue(context, focus, exp); 2753 case AliasAs : return funcAliasAs(context, focus, exp); 2754 case Alias : return funcAlias(context, focus, exp); 2755 case HtmlChecks : return funcHtmlChecks(context, focus, exp); 2756 case ToInteger : return funcToInteger(context, focus, exp); 2757 case ToDecimal : return funcToDecimal(context, focus, exp); 2758 case ToString : return funcToString(context, focus, exp); 2759 case ToBoolean : return funcToBoolean(context, focus, exp); 2760 case ToQuantity : return funcToQuantity(context, focus, exp); 2761 case ToDateTime : return funcToDateTime(context, focus, exp); 2762 case ToTime : return funcToTime(context, focus, exp); 2763 case ConvertsToInteger : return funcIsInteger(context, focus, exp); 2764 case ConvertsToDecimal : return funcIsDecimal(context, focus, exp); 2765 case ConvertsToString : return funcIsString(context, focus, exp); 2766 case ConvertsToBoolean : return funcIsBoolean(context, focus, exp); 2767 case ConvertsToQuantity : return funcIsQuantity(context, focus, exp); 2768 case ConvertsToDateTime : return funcIsDateTime(context, focus, exp); 2769 case ConvertsToTime : return funcIsTime(context, focus, exp); 2770 case ConformsTo : return funcConformsTo(context, focus, exp); 2771 case Custom: { 2772 List<List<Base>> params = new ArrayList<List<Base>>(); 2773 for (ExpressionNode p : exp.getParameters()) 2774 params.add(execute(context, focus, p, true)); 2775 return hostServices.executeFunction(context.appInfo, exp.getName(), params); 2776 } 2777 default: 2778 throw new Error("not Implemented yet"); 2779 } 2780 } 2781 2782 private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2783 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2784 String name = nl.get(0).primitiveValue(); 2785 context.addAlias(name, focus); 2786 return focus; 2787 } 2788 2789 private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2790 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2791 String name = nl.get(0).primitiveValue(); 2792 List<Base> res = new ArrayList<Base>(); 2793 Base b = context.getAlias(name); 2794 if (b != null) 2795 res.add(b); 2796 return res; 2797 } 2798 2799 private List<Base> funcHtmlChecks(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2800 // todo: actually check the HTML 2801 return makeBoolean(true); 2802 } 2803 2804 2805 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2806 List<Base> result = new ArrayList<Base>(); 2807 if (exp.getParameters().size() == 1) { 2808 List<Base> pc = new ArrayList<Base>(); 2809 boolean all = true; 2810 for (Base item : focus) { 2811 pc.clear(); 2812 pc.add(item); 2813 Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 2814 if (eq != Equality.True) { 2815 all = false; 2816 break; 2817 } 2818 } 2819 result.add(new BooleanType(all).noExtensions()); 2820 } else {// (exp.getParameters().size() == 0) { 2821 boolean all = true; 2822 for (Base item : focus) { 2823 Equality eq = asBool(item); 2824 if (eq != Equality.True) { 2825 all = false; 2826 break; 2827 } 2828 } 2829 result.add(new BooleanType(all).noExtensions()); 2830 } 2831 return result; 2832 } 2833 2834 2835 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 2836 return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, newThis); 2837 } 2838 2839 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 2840 return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 2841 } 2842 2843 2844 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2845 List<Base> result = new ArrayList<Base>(); 2846 result.add(DateTimeType.now()); 2847 return result; 2848 } 2849 2850 2851 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2852 List<Base> result = new ArrayList<Base>(); 2853 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 2854 return result; 2855 } 2856 2857 2858 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2859 throw new Error("not Implemented yet"); 2860 } 2861 2862 2863 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2864 List<Base> result = new ArrayList<Base>(); 2865 List<Base> current = new ArrayList<Base>(); 2866 current.addAll(focus); 2867 List<Base> added = new ArrayList<Base>(); 2868 boolean more = true; 2869 while (more) { 2870 added.clear(); 2871 for (Base item : current) { 2872 getChildrenByName(item, "*", added); 2873 } 2874 more = !added.isEmpty(); 2875 result.addAll(added); 2876 current.clear(); 2877 current.addAll(added); 2878 } 2879 return result; 2880 } 2881 2882 2883 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2884 List<Base> result = new ArrayList<Base>(); 2885 for (Base b : focus) 2886 getChildrenByName(b, "*", result); 2887 return result; 2888 } 2889 2890 2891 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException { 2892 List<Base> result = new ArrayList<Base>(); 2893 2894 if (focus.size() == 1) { 2895 String f = convertToString(focus.get(0)); 2896 2897 if (!Utilities.noString(f)) { 2898 2899 if (exp.getParameters().size() == 2) { 2900 2901 String t = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2902 String r = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2903 2904 String n = f.replace(t, r); 2905 result.add(new StringType(n)); 2906 } 2907 else { 2908 throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size())); 2909 } 2910 } 2911 else { 2912 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item")); 2913 } 2914 } 2915 else { 2916 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size())); 2917 } 2918 return result; 2919 } 2920 2921 2922 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2923 List<Base> result = new ArrayList<Base>(); 2924 String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2925 String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2926 2927 if (focus.size() == 1 && !Utilities.noString(regex)) 2928 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); 2929 else 2930 result.add(new StringType(convertToString(focus.get(0))).noExtensions()); 2931 return result; 2932 } 2933 2934 2935 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2936 List<Base> result = new ArrayList<Base>(); 2937 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2938 2939 if (focus.size() == 0) 2940 result.add(new BooleanType(false).noExtensions()); 2941 else if (Utilities.noString(sw)) 2942 result.add(new BooleanType(true).noExtensions()); 2943 else { 2944 if (focus.size() == 1 && !Utilities.noString(sw)) 2945 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 2946 else 2947 result.add(new BooleanType(false).noExtensions()); 2948 } 2949 return result; 2950 } 2951 2952 2953 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2954 List<Base> result = new ArrayList<Base>(); 2955 result.add(new StringType(convertToString(focus)).noExtensions()); 2956 return result; 2957 } 2958 2959 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2960 List<Base> result = new ArrayList<Base>(); 2961 if (focus.size() == 1) { 2962 if (focus.get(0) instanceof BooleanType) 2963 result.add(focus.get(0)); 2964 else if (focus.get(0) instanceof IntegerType) { 2965 int i = Integer.parseInt(focus.get(0).primitiveValue()); 2966 if (i == 0) 2967 result.add(new BooleanType(false).noExtensions()); 2968 else if (i == 1) 2969 result.add(new BooleanType(true).noExtensions()); 2970 } else if (focus.get(0) instanceof DecimalType) { 2971 if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) 2972 result.add(new BooleanType(false).noExtensions()); 2973 else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) 2974 result.add(new BooleanType(true).noExtensions()); 2975 } else if (focus.get(0) instanceof StringType) { 2976 if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) 2977 result.add(new BooleanType(true).noExtensions()); 2978 else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) 2979 result.add(new BooleanType(false).noExtensions()); 2980 } 2981 } 2982 return result; 2983 } 2984 2985 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2986 List<Base> result = new ArrayList<Base>(); 2987 if (focus.size() == 1) { 2988 if (focus.get(0) instanceof Quantity) 2989 result.add(focus.get(0)); 2990 else if (focus.get(0) instanceof StringType) { 2991 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 2992 if (q != null) 2993 result.add(q.noExtensions()); 2994 } else if (focus.get(0) instanceof IntegerType) { 2995 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2996 } else if (focus.get(0) instanceof DecimalType) { 2997 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2998 } 2999 } 3000 return result; 3001 } 3002 3003 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3004// List<Base> result = new ArrayList<Base>(); 3005// result.add(new BooleanType(convertToBoolean(focus))); 3006// return result; 3007 throw new NotImplementedException("funcToDateTime is not implemented"); 3008} 3009 3010 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3011// List<Base> result = new ArrayList<Base>(); 3012// result.add(new BooleanType(convertToBoolean(focus))); 3013// return result; 3014 throw new NotImplementedException("funcToTime is not implemented"); 3015} 3016 3017 3018 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3019 String s = convertToString(focus); 3020 List<Base> result = new ArrayList<Base>(); 3021 if (Utilities.isDecimal(s, true)) 3022 result.add(new DecimalType(s).noExtensions()); 3023 if ("true".equals(s)) 3024 result.add(new DecimalType(1).noExtensions()); 3025 if ("false".equals(s)) 3026 result.add(new DecimalType(0).noExtensions()); 3027 return result; 3028 } 3029 3030 3031 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3032 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3033 Equality v = asBool(n1); 3034 3035 if (v == Equality.True) 3036 return execute(context, focus, exp.getParameters().get(1), true); 3037 else if (exp.getParameters().size() < 3) 3038 return new ArrayList<Base>(); 3039 else 3040 return execute(context, focus, exp.getParameters().get(2), true); 3041 } 3042 3043 3044 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3045 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3046 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3047 3048 List<Base> result = new ArrayList<Base>(); 3049 for (int i = 0; i < Math.min(focus.size(), i1); i++) 3050 result.add(focus.get(i)); 3051 return result; 3052 } 3053 3054 3055 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3056 List<Base> result = new ArrayList<Base>(); 3057 for (Base item : focus) { 3058 if (!doContains(result, item)) 3059 result.add(item); 3060 } 3061 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 3062 if (!doContains(result, item)) 3063 result.add(item); 3064 } 3065 return result; 3066 } 3067 3068 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3069 List<Base> result = new ArrayList<Base>(); 3070 for (Base item : focus) { 3071 result.add(item); 3072 } 3073 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 3074 result.add(item); 3075 } 3076 return result; 3077 } 3078 3079 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3080 List<Base> result = new ArrayList<Base>(); 3081 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 3082 3083 for (Base item : focus) { 3084 if (!doContains(result, item) && doContains(other, item)) 3085 result.add(item); 3086 } 3087 return result; 3088 } 3089 3090 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3091 List<Base> result = new ArrayList<Base>(); 3092 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 3093 3094 for (Base item : focus) { 3095 if (!doContains(other, item)) 3096 result.add(item); 3097 } 3098 return result; 3099 } 3100 3101 3102 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3103 if (focus.size() == 1) 3104 return focus; 3105 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 3106 } 3107 3108 3109 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3110 if (focus.size() == 0 || focus.size() > 1) 3111 return makeBoolean(false); 3112 String ns = null; 3113 String n = null; 3114 3115 ExpressionNode texp = exp.getParameters().get(0); 3116 if (texp.getKind() != Kind.Name) 3117 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 3118 if (texp.getInner() != null) { 3119 if (texp.getInner().getKind() != Kind.Name) 3120 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 3121 ns = texp.getName(); 3122 n = texp.getInner().getName(); 3123 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { 3124 ns = "System"; 3125 n = texp.getName(); 3126 } else { 3127 ns = "FHIR"; 3128 n = texp.getName(); 3129 } 3130 if (ns.equals("System")) { 3131 if (focus.get(0) instanceof Resource) 3132 return makeBoolean(false); 3133 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) 3134 return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType()))); 3135 else 3136 return makeBoolean(false); 3137 } else if (ns.equals("FHIR")) { 3138 return makeBoolean(n.equals(focus.get(0).fhirType())); 3139 } else { 3140 return makeBoolean(false); 3141 } 3142 } 3143 3144 3145 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3146 List<Base> result = new ArrayList<Base>(); 3147 String tn; 3148 if (exp.getParameters().get(0).getInner() != null) 3149 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 3150 else 3151 tn = "FHIR."+exp.getParameters().get(0).getName(); 3152 for (Base b : focus) { 3153 if (tn.startsWith("System.")) { 3154 if (b instanceof Element &&((Element) b).isDisallowExtensions()) 3155 if (b.hasType(tn.substring(7))) 3156 result.add(b); 3157 } else if (tn.startsWith("FHIR.")) { 3158 if (b.hasType(tn.substring(5))) 3159 result.add(b); 3160 } 3161 } 3162 return result; 3163 } 3164 3165 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3166 List<Base> result = new ArrayList<Base>(); 3167 for (Base item : focus) 3168 result.add(new ClassTypeInfo(item)); 3169 return result; 3170 } 3171 3172 3173 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3174 List<Base> result = new ArrayList<Base>(); 3175 List<Base> current = new ArrayList<Base>(); 3176 current.addAll(focus); 3177 List<Base> added = new ArrayList<Base>(); 3178 boolean more = true; 3179 while (more) { 3180 added.clear(); 3181 List<Base> pc = new ArrayList<Base>(); 3182 for (Base item : current) { 3183 pc.clear(); 3184 pc.add(item); 3185 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 3186 } 3187 more = !added.isEmpty(); 3188 result.addAll(added); 3189 current.clear(); 3190 current.addAll(added); 3191 } 3192 return result; 3193 } 3194 3195 3196 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3197 List<Base> total = new ArrayList<Base>(); 3198 if (exp.parameterCount() > 1) 3199 total = execute(context, focus, exp.getParameters().get(1), false); 3200 3201 List<Base> pc = new ArrayList<Base>(); 3202 for (Base item : focus) { 3203 ExecutionContext c = changeThis(context, item); 3204 c.total = total; 3205 total = execute(c, pc, exp.getParameters().get(0), true); 3206 } 3207 return total; 3208 } 3209 3210 3211 3212 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3213 if (focus.size() < 1) 3214 return makeBoolean(true); 3215 if (focus.size() == 1) 3216 return makeBoolean(true); 3217 3218 boolean distinct = true; 3219 for (int i = 0; i < focus.size(); i++) { 3220 for (int j = i+1; j < focus.size(); j++) { 3221 Boolean eq = doEquals(focus.get(j), focus.get(i)); 3222 if (eq == null) { 3223 return new ArrayList<Base>(); 3224 } else if (eq == true) { 3225 distinct = false; 3226 break; 3227 } 3228 } 3229 } 3230 return makeBoolean(distinct); 3231 } 3232 3233 3234 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3235 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 3236 3237 boolean valid = true; 3238 for (Base item : target) { 3239 boolean found = false; 3240 for (Base t : focus) { 3241 if (Base.compareDeep(item, t, false)) { 3242 found = true; 3243 break; 3244 } 3245 } 3246 if (!found) { 3247 valid = false; 3248 break; 3249 } 3250 } 3251 List<Base> result = new ArrayList<Base>(); 3252 result.add(new BooleanType(valid).noExtensions()); 3253 return result; 3254 } 3255 3256 3257 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3258 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 3259 3260 boolean valid = true; 3261 for (Base item : focus) { 3262 boolean found = false; 3263 for (Base t : target) { 3264 if (Base.compareDeep(item, t, false)) { 3265 found = true; 3266 break; 3267 } 3268 } 3269 if (!found) { 3270 valid = false; 3271 break; 3272 } 3273 } 3274 List<Base> result = new ArrayList<Base>(); 3275 result.add(new BooleanType(valid).noExtensions()); 3276 return result; 3277 } 3278 3279 3280 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3281 List<Base> result = new ArrayList<Base>(); 3282 boolean empty = true; 3283 for (Base f : focus) 3284 if (!f.isEmpty()) 3285 empty = false; 3286 result.add(new BooleanType(!empty).noExtensions()); 3287 return result; 3288 } 3289 3290 3291 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3292 List<Base> result = new ArrayList<Base>(); 3293 for (Base item : focus) { 3294 String s = convertToString(item); 3295 if (item.fhirType().equals("Reference")) { 3296 Property p = item.getChildByName("reference"); 3297 if (p != null && p.hasValues()) 3298 s = convertToString(p.getValues().get(0)); 3299 else 3300 s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) 3301 } 3302 if (item.fhirType().equals("canonical")) { 3303 s = item.primitiveValue(); 3304 } 3305 if (s != null) { 3306 Base res = null; 3307 if (s.startsWith("#")) { 3308 Property p = context.rootResource.getChildByName("contained"); 3309 for (Base c : p.getValues()) { 3310 if (chompHash(s).equals(chompHash(c.getIdBase()))) { 3311 res = c; 3312 break; 3313 } 3314 } 3315 } else if (hostServices != null) { 3316 res = hostServices.resolveReference(context.appInfo, s); 3317 } 3318 if (res != null) 3319 result.add(res); 3320 } 3321 } 3322 3323 return result; 3324 } 3325 3326 /** 3327 * Strips a leading hashmark (#) if present at the start of a string 3328 */ 3329 private String chompHash(String theId) { 3330 String retVal = theId; 3331 while (retVal.startsWith("#")) { 3332 retVal = retVal.substring(1); 3333 } 3334 return retVal; 3335 } 3336 3337 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3338 List<Base> result = new ArrayList<Base>(); 3339 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 3340 String url = nl.get(0).primitiveValue(); 3341 3342 for (Base item : focus) { 3343 List<Base> ext = new ArrayList<Base>(); 3344 getChildrenByName(item, "extension", ext); 3345 getChildrenByName(item, "modifierExtension", ext); 3346 for (Base ex : ext) { 3347 List<Base> vl = new ArrayList<Base>(); 3348 getChildrenByName(ex, "url", vl); 3349 if (convertToString(vl).equals(url)) 3350 result.add(ex); 3351 } 3352 } 3353 return result; 3354 } 3355 3356 private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3357 List<Base> result = new ArrayList<Base>(); 3358 if (exp.getParameters().size() == 1) { 3359 boolean all = true; 3360 List<Base> pc = new ArrayList<Base>(); 3361 for (Base item : focus) { 3362 pc.clear(); 3363 pc.add(item); 3364 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3365 Equality v = asBool(res); 3366 if (v != Equality.False) { 3367 all = false; 3368 break; 3369 } 3370 } 3371 result.add(new BooleanType(all).noExtensions()); 3372 } else { 3373 boolean all = true; 3374 for (Base item : focus) { 3375 Equality v = asBool(item); 3376 if (v != Equality.False) { 3377 all = false; 3378 break; 3379 } 3380 } 3381 result.add(new BooleanType(all).noExtensions()); 3382 } 3383 return result; 3384 } 3385 3386 private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3387 List<Base> result = new ArrayList<Base>(); 3388 if (exp.getParameters().size() == 1) { 3389 boolean any = false; 3390 List<Base> pc = new ArrayList<Base>(); 3391 for (Base item : focus) { 3392 pc.clear(); 3393 pc.add(item); 3394 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3395 Equality v = asBool(res); 3396 if (v == Equality.False) { 3397 any = true; 3398 break; 3399 } 3400 } 3401 result.add(new BooleanType(any).noExtensions()); 3402 } else { 3403 boolean any = false; 3404 for (Base item : focus) { 3405 Equality v = asBool(item); 3406 if (v == Equality.False) { 3407 any = true; 3408 break; 3409 } 3410 } 3411 result.add(new BooleanType(any).noExtensions()); 3412 } 3413 return result; 3414 } 3415 3416 private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3417 List<Base> result = new ArrayList<Base>(); 3418 if (exp.getParameters().size() == 1) { 3419 boolean all = true; 3420 List<Base> pc = new ArrayList<Base>(); 3421 for (Base item : focus) { 3422 pc.clear(); 3423 pc.add(item); 3424 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3425 Equality v = asBool(res); 3426 if (v != Equality.True) { 3427 all = false; 3428 break; 3429 } 3430 } 3431 result.add(new BooleanType(all).noExtensions()); 3432 } else { 3433 boolean all = true; 3434 for (Base item : focus) { 3435 Equality v = asBool(item); 3436 if (v != Equality.True) { 3437 all = false; 3438 break; 3439 } 3440 } 3441 result.add(new BooleanType(all).noExtensions()); 3442 } 3443 return result; 3444 } 3445 3446 private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3447 List<Base> result = new ArrayList<Base>(); 3448 if (exp.getParameters().size() == 1) { 3449 boolean any = false; 3450 List<Base> pc = new ArrayList<Base>(); 3451 for (Base item : focus) { 3452 pc.clear(); 3453 pc.add(item); 3454 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3455 Equality v = asBool(res); 3456 if (v == Equality.True) { 3457 any = true; 3458 break; 3459 } 3460 } 3461 result.add(new BooleanType(any).noExtensions()); 3462 } else { 3463 boolean any = false; 3464 for (Base item : focus) { 3465 Equality v = asBool(item); 3466 if (v == Equality.True) { 3467 any = true; 3468 break; 3469 } 3470 } 3471 result.add(new BooleanType(any).noExtensions()); 3472 } 3473 return result; 3474 } 3475 3476 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3477 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 3478 String name = nl.get(0).primitiveValue(); 3479 if (exp.getParameters().size() == 2) { 3480 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 3481 log(name, n2); 3482 } else 3483 log(name, focus); 3484 return focus; 3485 } 3486 3487 private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3488 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3489 if (!convertToBoolean(n1)) { 3490 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 3491 String name = n2.get(0).primitiveValue(); 3492 throw new FHIRException("check failed: "+name); 3493 } 3494 return focus; 3495 } 3496 3497 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3498 if (focus.size() <= 1) 3499 return focus; 3500 3501 List<Base> result = new ArrayList<Base>(); 3502 for (int i = 0; i < focus.size(); i++) { 3503 boolean found = false; 3504 for (int j = i+1; j < focus.size(); j++) { 3505 Boolean eq = doEquals(focus.get(j), focus.get(i)); 3506 if (eq == null) 3507 return new ArrayList<Base>(); 3508 else if (eq == true) { 3509 found = true; 3510 break; 3511 } 3512 } 3513 if (!found) 3514 result.add(focus.get(i)); 3515 } 3516 return result; 3517 } 3518 3519 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3520 List<Base> result = new ArrayList<Base>(); 3521 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3522 3523 if (focus.size() == 1 && !Utilities.noString(sw)) { 3524 String st = convertToString(focus.get(0)); 3525 if (Utilities.noString(st)) 3526 result.add(new BooleanType(false).noExtensions()); 3527 else { 3528 boolean ok = st.matches(sw); 3529 result.add(new BooleanType(ok).noExtensions()); 3530 } 3531 } else 3532 result.add(new BooleanType(false).noExtensions()); 3533 return result; 3534 } 3535 3536 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3537 List<Base> result = new ArrayList<Base>(); 3538 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3539 3540 if (focus.size() != 1) { 3541 result.add(new BooleanType(false).noExtensions()); 3542 } else if (Utilities.noString(sw)) { 3543 result.add(new BooleanType(true).noExtensions()); 3544 } else { 3545 String st = convertToString(focus.get(0)); 3546 if (Utilities.noString(st)) 3547 result.add(new BooleanType(false).noExtensions()); 3548 else 3549 result.add(new BooleanType(st.contains(sw)).noExtensions()); 3550 } 3551 return result; 3552 } 3553 3554 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3555 List<Base> result = new ArrayList<Base>(); 3556 if (focus.size() == 1) { 3557 String s = convertToString(focus.get(0)); 3558 result.add(new IntegerType(s.length()).noExtensions()); 3559 } 3560 return result; 3561 } 3562 3563 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3564 List<Base> result = new ArrayList<Base>(); 3565 if (focus.size() == 1) { 3566 String s = convertToString(focus.get(0)); 3567 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 3568 } else 3569 result.add(new BooleanType(false).noExtensions()); 3570 return result; 3571 } 3572 3573 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3574 List<Base> result = new ArrayList<Base>(); 3575 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3576 3577 if (focus.size() == 0) { 3578 result.add(new BooleanType(false).noExtensions()); 3579 } else if (Utilities.noString(sw)) { 3580 result.add(new BooleanType(true).noExtensions()); 3581 } else { 3582 String s = convertToString(focus.get(0)); 3583 if (s == null) 3584 result.add(new BooleanType(false).noExtensions()); 3585 else 3586 result.add(new BooleanType(s.startsWith(sw)).noExtensions()); 3587 } 3588 return result; 3589 } 3590 3591 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3592 List<Base> result = new ArrayList<Base>(); 3593 if (focus.size() == 1) { 3594 String s = convertToString(focus.get(0)); 3595 if (!Utilities.noString(s)) 3596 result.add(new StringType(s.toLowerCase()).noExtensions()); 3597 } 3598 return result; 3599 } 3600 3601 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3602 List<Base> result = new ArrayList<Base>(); 3603 if (focus.size() == 1) { 3604 String s = convertToString(focus.get(0)); 3605 if (!Utilities.noString(s)) 3606 result.add(new StringType(s.toUpperCase()).noExtensions()); 3607 } 3608 return result; 3609 } 3610 3611 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3612 List<Base> result = new ArrayList<Base>(); 3613 if (focus.size() == 1) { 3614 String s = convertToString(focus.get(0)); 3615 for (char c : s.toCharArray()) 3616 result.add(new StringType(String.valueOf(c)).noExtensions()); 3617 } 3618 return result; 3619 } 3620 3621 private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3622 List<Base> result = new ArrayList<Base>(); 3623 3624 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3625 if (focus.size() == 0) { 3626 result.add(new IntegerType(0).noExtensions()); 3627 } else if (Utilities.noString(sw)) { 3628 result.add(new IntegerType(0).noExtensions()); 3629 } else { 3630 String s = convertToString(focus.get(0)); 3631 if (s == null) 3632 result.add(new IntegerType(0).noExtensions()); 3633 else 3634 result.add(new IntegerType(s.indexOf(sw)).noExtensions()); 3635 } 3636 return result; 3637 } 3638 3639 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3640 List<Base> result = new ArrayList<Base>(); 3641 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3642 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3643 int i2 = -1; 3644 if (exp.parameterCount() == 2) { 3645 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 3646 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 3647 } 3648 3649 if (focus.size() == 1) { 3650 String sw = convertToString(focus.get(0)); 3651 String s; 3652 if (i1 < 0 || i1 >= sw.length()) 3653 return new ArrayList<Base>(); 3654 if (exp.parameterCount() == 2) 3655 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 3656 else 3657 s = sw.substring(i1); 3658 if (!Utilities.noString(s)) 3659 result.add(new StringType(s).noExtensions()); 3660 } 3661 return result; 3662 } 3663 3664 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3665 String s = convertToString(focus); 3666 List<Base> result = new ArrayList<Base>(); 3667 if (Utilities.isInteger(s)) 3668 result.add(new IntegerType(s).noExtensions()); 3669 else if ("true".equals(s)) 3670 result.add(new IntegerType(1).noExtensions()); 3671 else if ("false".equals(s)) 3672 result.add(new IntegerType(0).noExtensions()); 3673 return result; 3674 } 3675 3676 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3677 List<Base> result = new ArrayList<Base>(); 3678 if (focus.size() != 1) 3679 result.add(new BooleanType(false).noExtensions()); 3680 else if (focus.get(0) instanceof IntegerType) 3681 result.add(new BooleanType(true).noExtensions()); 3682 else if (focus.get(0) instanceof BooleanType) 3683 result.add(new BooleanType(true).noExtensions()); 3684 else if (focus.get(0) instanceof StringType) 3685 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 3686 else 3687 result.add(new BooleanType(false).noExtensions()); 3688 return result; 3689 } 3690 3691 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3692 List<Base> result = new ArrayList<Base>(); 3693 if (focus.size() != 1) 3694 result.add(new BooleanType(false).noExtensions()); 3695 else if (focus.get(0) instanceof IntegerType) 3696 result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); 3697 else if (focus.get(0) instanceof DecimalType) 3698 result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); 3699 else if (focus.get(0) instanceof BooleanType) 3700 result.add(new BooleanType(true).noExtensions()); 3701 else if (focus.get(0) instanceof StringType) 3702 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); 3703 else 3704 result.add(new BooleanType(false).noExtensions()); 3705 return result; 3706 } 3707 3708 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3709 List<Base> result = new ArrayList<Base>(); 3710 if (focus.size() != 1) 3711 result.add(new BooleanType(false).noExtensions()); 3712 else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) 3713 result.add(new BooleanType(true).noExtensions()); 3714 else if (focus.get(0) instanceof StringType) 3715 result.add(new BooleanType((convertToString(focus.get(0)).matches 3716 ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions()); 3717 else 3718 result.add(new BooleanType(false).noExtensions()); 3719 return result; 3720 } 3721 3722 private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3723 if (hostServices == null) 3724 throw new FHIRException("Unable to check conformsTo - no hostservices provided"); 3725 List<Base> result = new ArrayList<Base>(); 3726 if (focus.size() != 1) 3727 result.add(new BooleanType(false).noExtensions()); 3728 else { 3729 String url = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3730 result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo, focus.get(0), url)).noExtensions()); 3731 } 3732 return result; 3733 } 3734 3735 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3736 List<Base> result = new ArrayList<Base>(); 3737 if (focus.size() != 1) 3738 result.add(new BooleanType(false).noExtensions()); 3739 else if (focus.get(0) instanceof TimeType) 3740 result.add(new BooleanType(true).noExtensions()); 3741 else if (focus.get(0) instanceof StringType) 3742 result.add(new BooleanType((convertToString(focus.get(0)).matches 3743 ("T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))).noExtensions()); 3744 else 3745 result.add(new BooleanType(false).noExtensions()); 3746 return result; 3747 } 3748 3749 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3750 List<Base> result = new ArrayList<Base>(); 3751 if (focus.size() != 1) 3752 result.add(new BooleanType(false).noExtensions()); 3753 else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) 3754 result.add(new BooleanType(true).noExtensions()); 3755 else 3756 result.add(new BooleanType(false).noExtensions()); 3757 return result; 3758 } 3759 3760 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3761 List<Base> result = new ArrayList<Base>(); 3762 if (focus.size() != 1) 3763 result.add(new BooleanType(false).noExtensions()); 3764 else if (focus.get(0) instanceof IntegerType) 3765 result.add(new BooleanType(true).noExtensions()); 3766 else if (focus.get(0) instanceof DecimalType) 3767 result.add(new BooleanType(true).noExtensions()); 3768 else if (focus.get(0) instanceof Quantity) 3769 result.add(new BooleanType(true).noExtensions()); 3770 else if (focus.get(0) instanceof BooleanType) 3771 result.add(new BooleanType(true).noExtensions()); 3772 else if (focus.get(0) instanceof StringType) { 3773 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 3774 result.add(new BooleanType(q != null).noExtensions()); 3775 } else 3776 result.add(new BooleanType(false).noExtensions()); 3777 return result; 3778 } 3779 3780 public Quantity parseQuantityString(String s) { 3781 if (s == null) 3782 return null; 3783 s = s.trim(); 3784 if (s.contains(" ")) { 3785 String v = s.substring(0, s.indexOf(" ")).trim(); 3786 s = s.substring(s.indexOf(" ")).trim(); 3787 if (!Utilities.isDecimal(v, false)) 3788 return null; 3789 if (s.startsWith("'") && s.endsWith("'")) 3790 return Quantity.fromUcum(v, s.substring(1, s.length()-1)); 3791 if (s.equals("year") || s.equals("years")) 3792 return Quantity.fromUcum(v, "a"); 3793 else if (s.equals("month") || s.equals("months")) 3794 return Quantity.fromUcum(v, "mo"); 3795 else if (s.equals("week") || s.equals("weeks")) 3796 return Quantity.fromUcum(v, "wk"); 3797 else if (s.equals("day") || s.equals("days")) 3798 return Quantity.fromUcum(v, "d"); 3799 else if (s.equals("hour") || s.equals("hours")) 3800 return Quantity.fromUcum(v, "h"); 3801 else if (s.equals("minute") || s.equals("minutes")) 3802 return Quantity.fromUcum(v, "min"); 3803 else if (s.equals("second") || s.equals("seconds")) 3804 return Quantity.fromUcum(v, "s"); 3805 else if (s.equals("millisecond") || s.equals("milliseconds")) 3806 return Quantity.fromUcum(v, "ms"); 3807 else 3808 return null; 3809 } else { 3810 if (Utilities.isDecimal(s, true)) 3811 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 3812 else 3813 return null; 3814 } 3815 } 3816 3817 3818 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3819 List<Base> result = new ArrayList<Base>(); 3820 if (focus.size() != 1) 3821 result.add(new BooleanType(false).noExtensions()); 3822 else if (focus.get(0) instanceof IntegerType) 3823 result.add(new BooleanType(true).noExtensions()); 3824 else if (focus.get(0) instanceof BooleanType) 3825 result.add(new BooleanType(true).noExtensions()); 3826 else if (focus.get(0) instanceof DecimalType) 3827 result.add(new BooleanType(true).noExtensions()); 3828 else if (focus.get(0) instanceof StringType) 3829 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); 3830 else 3831 result.add(new BooleanType(false).noExtensions()); 3832 return result; 3833 } 3834 3835 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3836 List<Base> result = new ArrayList<Base>(); 3837 result.add(new IntegerType(focus.size()).noExtensions()); 3838 return result; 3839 } 3840 3841 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3842 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3843 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3844 3845 List<Base> result = new ArrayList<Base>(); 3846 for (int i = i1; i < focus.size(); i++) 3847 result.add(focus.get(i)); 3848 return result; 3849 } 3850 3851 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3852 List<Base> result = new ArrayList<Base>(); 3853 for (int i = 1; i < focus.size(); i++) 3854 result.add(focus.get(i)); 3855 return result; 3856 } 3857 3858 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3859 List<Base> result = new ArrayList<Base>(); 3860 if (focus.size() > 0) 3861 result.add(focus.get(focus.size()-1)); 3862 return result; 3863 } 3864 3865 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3866 List<Base> result = new ArrayList<Base>(); 3867 if (focus.size() > 0) 3868 result.add(focus.get(0)); 3869 return result; 3870 } 3871 3872 3873 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3874 List<Base> result = new ArrayList<Base>(); 3875 List<Base> pc = new ArrayList<Base>(); 3876 for (Base item : focus) { 3877 pc.clear(); 3878 pc.add(item); 3879 Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 3880 if (v == Equality.True) 3881 result.add(item); 3882 } 3883 return result; 3884 } 3885 3886 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3887 List<Base> result = new ArrayList<Base>(); 3888 List<Base> pc = new ArrayList<Base>(); 3889 for (Base item : focus) { 3890 pc.clear(); 3891 pc.add(item); 3892 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 3893 } 3894 return result; 3895 } 3896 3897 3898 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3899 List<Base> result = new ArrayList<Base>(); 3900 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3901 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 3902 result.add(focus.get(Integer.parseInt(s))); 3903 return result; 3904 } 3905 3906 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3907 List<Base> result = new ArrayList<Base>(); 3908 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 3909 return result; 3910 } 3911 3912 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3913 List<Base> result = new ArrayList<Base>(); 3914 Equality v = asBool(focus); 3915 if (v != Equality.Null) 3916 result.add(new BooleanType(v != Equality.True)); 3917 return result; 3918 } 3919 3920 public class ElementDefinitionMatch { 3921 private ElementDefinition definition; 3922 private String fixedType; 3923 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 3924 super(); 3925 this.definition = definition; 3926 this.fixedType = fixedType; 3927 } 3928 public ElementDefinition getDefinition() { 3929 return definition; 3930 } 3931 public String getFixedType() { 3932 return fixedType; 3933 } 3934 3935 } 3936 3937 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 3938 if (Utilities.noString(type)) 3939 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 3940 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) 3941 return; 3942 if (type.startsWith(Constants.NS_SYSTEM_TYPE)) 3943 return; 3944 3945 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 3946 getSimpleTypeChildTypesByName(name, result); 3947 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 3948 getClassInfoChildTypesByName(name, result); 3949 } else { 3950 String url = null; 3951 if (type.contains("#")) { 3952 url = type.substring(0, type.indexOf("#")); 3953 } else { 3954 url = type; 3955 } 3956 String tail = ""; 3957 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 3958 if (sd == null) 3959 throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 3960 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 3961 ElementDefinitionMatch m = null; 3962 if (type.contains("#")) 3963 m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false); 3964 if (m != null && hasDataType(m.definition)) { 3965 if (m.fixedType != null) 3966 { 3967 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs())); 3968 if (dt == null) 3969 throw new DefinitionException("unknown data type "+m.fixedType); 3970 sdl.add(dt); 3971 } else 3972 for (TypeRefComponent t : m.definition.getType()) { 3973 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs())); 3974 if (dt == null) 3975 throw new DefinitionException("unknown data type "+t.getCode()); 3976 sdl.add(dt); 3977 } 3978 } else { 3979 sdl.add(sd); 3980 if (type.contains("#")) { 3981 tail = type.substring(type.indexOf("#")+1); 3982 tail = tail.substring(tail.indexOf(".")); 3983 } 3984 } 3985 3986 for (StructureDefinition sdi : sdl) { 3987 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 3988 if (name.equals("**")) { 3989 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 3990 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 3991 if (ed.getPath().startsWith(path)) 3992 for (TypeRefComponent t : ed.getType()) { 3993 if (t.hasCode() && t.getCodeElement().hasValue()) { 3994 String tn = null; 3995 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 3996 tn = sdi.getType()+"#"+ed.getPath(); 3997 else 3998 tn = t.getCode(); 3999 if (t.getCode().equals("Resource")) { 4000 for (String rn : worker.getResourceNames()) { 4001 if (!result.hasType(worker, rn)) { 4002 getChildTypesByName(result.addType(rn), "**", result); 4003 } 4004 } 4005 } else if (!result.hasType(worker, tn)) { 4006 getChildTypesByName(result.addType(tn), "**", result); 4007 } 4008 } 4009 } 4010 } 4011 } else if (name.equals("*")) { 4012 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 4013 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 4014 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 4015 for (TypeRefComponent t : ed.getType()) { 4016 if (Utilities.noString(t.getCode())) // Element.id or Extension.url 4017 result.addType("System.string"); 4018 else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 4019 result.addType(sdi.getType()+"#"+ed.getPath()); 4020 else if (t.getCode().equals("Resource")) 4021 result.addTypes(worker.getResourceNames()); 4022 else 4023 result.addType(t.getCode()); 4024 } 4025 } 4026 } else { 4027 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 4028 4029 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 4030 if (ed != null) { 4031 if (!Utilities.noString(ed.getFixedType())) 4032 result.addType(ed.getFixedType()); 4033 else 4034 for (TypeRefComponent t : ed.getDefinition().getType()) { 4035 if (Utilities.noString(t.getCode())) { 4036 if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) 4037 result.addType(TypeDetails.FP_NS, "string"); 4038 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 4039 } 4040 4041 ProfiledType pt = null; 4042 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 4043 pt = new ProfiledType(sdi.getUrl()+"#"+path); 4044 else if (t.getCode().equals("Resource")) 4045 result.addTypes(worker.getResourceNames()); 4046 else 4047 pt = new ProfiledType(t.getCode()); 4048 if (pt != null) { 4049 if (t.hasProfile()) 4050 pt.addProfiles(t.getProfile()); 4051 if (ed.getDefinition().hasBinding()) 4052 pt.addBinding(ed.getDefinition().getBinding()); 4053 result.addType(pt); 4054 } 4055 } 4056 } 4057 } 4058 } 4059 } 4060 } 4061 4062 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 4063 if (name.equals("namespace")) 4064 result.addType(TypeDetails.FP_String); 4065 if (name.equals("name")) 4066 result.addType(TypeDetails.FP_String); 4067 } 4068 4069 4070 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 4071 if (name.equals("namespace")) 4072 result.addType(TypeDetails.FP_String); 4073 if (name.equals("name")) 4074 result.addType(TypeDetails.FP_String); 4075 } 4076 4077 4078 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 4079 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4080 if (ed.getPath().equals(path)) { 4081 if (ed.hasContentReference()) { 4082 return getElementDefinitionById(sd, ed.getContentReference()); 4083 } else 4084 return new ElementDefinitionMatch(ed, null); 4085 } 4086 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) 4087 return new ElementDefinitionMatch(ed, null); 4088 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 4089 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 4090 if (primitiveTypes.contains(s)) 4091 return new ElementDefinitionMatch(ed, s); 4092 else 4093 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); 4094 } 4095 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 4096 // now we walk into the type. 4097 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 4098 throw new PathEngineException("Internal typing issue...."); 4099 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); 4100 if (nsd == null) 4101 throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode()); 4102 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName); 4103 } 4104 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 4105 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 4106 return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName); 4107 } 4108 } 4109 return null; 4110 } 4111 4112 private boolean isAbstractType(List<TypeRefComponent> list) { 4113 return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 4114} 4115 4116 4117 private boolean hasType(ElementDefinition ed, String s) { 4118 for (TypeRefComponent t : ed.getType()) 4119 if (s.equalsIgnoreCase(t.getCode())) 4120 return true; 4121 return false; 4122 } 4123 4124 private boolean hasDataType(ElementDefinition ed) { 4125 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 4126 } 4127 4128 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 4129 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4130 if (ref.equals("#"+ed.getId())) 4131 return new ElementDefinitionMatch(ed, null); 4132 } 4133 return null; 4134 } 4135 4136 4137 public boolean hasLog() { 4138 return log != null && log.length() > 0; 4139 } 4140 4141 4142 public String takeLog() { 4143 if (!hasLog()) 4144 return ""; 4145 String s = log.toString(); 4146 log = new StringBuilder(); 4147 return s; 4148 } 4149 4150 4151 /** given an element definition in a profile, what element contains the differentiating fixed 4152 * for the element, given the differentiating expresssion. The expression is only allowed to 4153 * use a subset of FHIRPath 4154 * 4155 * @param profile 4156 * @param element 4157 * @return 4158 * @throws PathEngineException 4159 * @throws DefinitionException 4160 */ 4161 public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException { 4162 StructureDefinition sd = profile; 4163 ElementDefinition focus = null; 4164 4165 if (expr.getKind() == Kind.Name) { 4166 if (element.hasSlicing()) { 4167 ElementDefinition slice = pickMandatorySlice(sd, element); 4168 if (slice == null) 4169 throw new DefinitionException("Error in discriminator at "+element.getId()+": found a sliced element while resolving the fixed value for one of the slices"); 4170 element = slice; 4171 } 4172 4173 if (expr.getName().equals("$this")) { 4174 focus = element; 4175 } else { 4176 List<ElementDefinition> childDefinitions; 4177 childDefinitions = ProfileUtilities.getChildMap(sd, element); 4178 // if that's empty, get the children of the type 4179 if (childDefinitions.isEmpty()) { 4180 sd = fetchStructureByType(element); 4181 if (sd == null) 4182 throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getProfile()+"' on "+element.getId()+" could not be resolved"); 4183 childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 4184 } 4185 for (ElementDefinition t : childDefinitions) { 4186 if (tailMatches(t, expr.getName())) { 4187 focus = t; 4188 break; 4189 } 4190 } 4191 } 4192 } else if (expr.getKind() == Kind.Function) { 4193 if ("resolve".equals(expr.getName())) { 4194 if (!element.hasType()) 4195 throw new DefinitionException("illegal use of resolve() in discriminator - no type on element "+element.getId()); 4196 if (element.getType().size() > 1) 4197 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on "+element.getId()); 4198 if (!element.getType().get(0).hasTarget()) 4199 throw new DefinitionException("illegal use of resolve() in discriminator - type on "+element.getId()+" is not Reference ("+element.getType().get(0).getCode()+")"); 4200 if (element.getType().get(0).getTargetProfile().size() > 1) 4201 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on "+element.getId()); 4202 sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue()); 4203 if (sd == null) 4204 throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getTargetProfile()+"' on "+element.getId()+" could not be resolved"); 4205 focus = sd.getSnapshot().getElementFirstRep(); 4206 } else if ("extension".equals(expr.getName())) { 4207 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 4208// targetUrl = targetUrl.substring(1,targetUrl.length()-1); 4209 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(sd, element); 4210 for (ElementDefinition t : childDefinitions) { 4211 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 4212 StructureDefinition exsd = worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue()); 4213 while (exsd!=null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) 4214 exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition()); 4215 if (exsd.getUrl().equals(targetUrl)) { 4216 if (ProfileUtilities.getChildMap(sd, t).isEmpty()) 4217 sd = exsd; 4218 focus = t; 4219 break; 4220 } 4221 } 4222 } 4223 } else 4224 throw new DefinitionException("illegal function name "+expr.getName()+"() in discriminator"); 4225 } else if (expr.getKind() == Kind.Group) { 4226 throw new DefinitionException("illegal expression syntax in discriminator (group)"); 4227 } else if (expr.getKind() == Kind.Constant) { 4228 throw new DefinitionException("illegal expression syntax in discriminator (const)"); 4229 } 4230 4231 if (focus == null) 4232 throw new DefinitionException("Unable to resolve discriminator in definitions: "+expr.toString()); 4233 else if (expr.getInner() == null) 4234 return focus; 4235 else { 4236 return evaluateDefinition(expr.getInner(), sd, focus); 4237 } 4238 } 4239 4240 private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException { 4241 List<ElementDefinition> list = ProfileUtilities.getSliceList(sd, element); 4242 for (ElementDefinition ed : list) { 4243 if (ed.getMin() > 0) 4244 return ed; 4245 } 4246 return null; 4247 } 4248 4249 4250 private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException { 4251 if (ed.getType().size() == 0) 4252 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, no type"); 4253 if (ed.getType().size() > 1) 4254 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple types"); 4255 if (ed.getType().get(0).getProfile().size() > 1) 4256 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple type profiles"); 4257 if (ed.getType().get(0).hasProfile()) 4258 return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); 4259 else 4260 return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); 4261 } 4262 4263 4264 private boolean tailMatches(ElementDefinition t, String d) { 4265 String tail = tailDot(t.getPath()); 4266 if (d.contains("[")) 4267 return tail.startsWith(d.substring(0, d.indexOf('['))); 4268 else if (tail.equals(d)) 4269 return true; 4270 else if (t.getType().size() == 1 && t.getType().get(0).getCode() != null && t.getPath() != null && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) 4271 return tail.startsWith(d); 4272 else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) 4273 return true; 4274 return false; 4275 } 4276 4277 private String tailDot(String path) { 4278 return path.substring(path.lastIndexOf(".") + 1); 4279 } 4280 4281 private Equality asBool(List<Base> items) throws PathEngineException { 4282 if (items.size() == 0) 4283 return Equality.Null; 4284 else if (items.size() == 1) 4285 return asBool(items.get(0)); 4286 else 4287 throw new PathEngineException("Unable to evaluate as a boolean: "+convertToString(items)); 4288 } 4289 4290 private Equality asBoolFromInt(String s) { 4291 try { 4292 int i = Integer.parseInt(s); 4293 switch (i) { 4294 case 0: return Equality.False; 4295 case 1: return Equality.True; 4296 default: return Equality.Null; 4297 } 4298 } catch (Exception e) { 4299 return Equality.Null; 4300 } 4301 } 4302 4303 private Equality asBoolFromDec(String s) { 4304 try { 4305 BigDecimal d = new BigDecimal(s); 4306 if (d.compareTo(BigDecimal.ZERO) == 0) 4307 return Equality.False; 4308 else if (d.compareTo(BigDecimal.ONE) == 0) 4309 return Equality.True; 4310 else 4311 return Equality.Null; 4312 } catch (Exception e) { 4313 return Equality.Null; 4314 } 4315 } 4316 4317 private Equality asBool(Base item) { 4318 if (item instanceof BooleanType) 4319 return boolToTriState(((BooleanType) item).booleanValue()); 4320 else if (item.isBooleanPrimitive()) { 4321 if (Utilities.existsInList(item.primitiveValue(), "true")) 4322 return Equality.True; 4323 else if (Utilities.existsInList(item.primitiveValue(), "false")) 4324 return Equality.False; 4325 else 4326 return Equality.Null; 4327 } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) 4328 return asBoolFromInt(item.primitiveValue()); 4329 else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) 4330 return asBoolFromDec(item.primitiveValue()); 4331 else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { 4332 if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) 4333 return Equality.True; 4334 else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) 4335 return Equality.False; 4336 else if (Utilities.isInteger(item.primitiveValue())) 4337 return asBoolFromInt(item.primitiveValue()); 4338 else if (Utilities.isDecimal(item.primitiveValue(), true)) 4339 return asBoolFromDec(item.primitiveValue()); 4340 else 4341 return Equality.Null; 4342 } 4343 return Equality.Null; 4344 } 4345 4346 private Equality boolToTriState(boolean b) { 4347 return b ? Equality.True : Equality.False; 4348 } 4349 4350 4351 public TerminologyServiceOptions getTerminologyServiceOptions() { 4352 return terminologyServiceOptions; 4353 } 4354 4355 4356 public IWorkerContext getWorker() { 4357 return worker; 4358 } 4359 4360}