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