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