
001package org.hl7.fhir.r5.fhirpath; 002 003import java.math.BigDecimal; 004import java.math.RoundingMode; 005import java.util.ArrayList; 006import java.util.Arrays; 007import java.util.Base64; 008import java.util.Calendar; 009import java.util.Collections; 010import java.util.Comparator; 011import java.util.Date; 012import java.util.EnumSet; 013import java.util.HashMap; 014import java.util.HashSet; 015import java.util.List; 016import java.util.Map; 017import java.util.Set; 018import java.util.regex.Matcher; 019import java.util.regex.Pattern; 020 021import lombok.extern.slf4j.Slf4j; 022import org.fhir.ucum.Decimal; 023import org.fhir.ucum.Pair; 024import org.fhir.ucum.UcumException; 025import org.hl7.fhir.exceptions.DefinitionException; 026import org.hl7.fhir.exceptions.FHIRException; 027import org.hl7.fhir.exceptions.PathEngineException; 028import org.hl7.fhir.instance.model.api.IIdType; 029import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 030import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; 031import org.hl7.fhir.r5.context.ContextUtilities; 032import org.hl7.fhir.r5.context.IWorkerContext; 033import org.hl7.fhir.r5.elementmodel.ObjectConverter; 034import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; 035import org.hl7.fhir.r5.fhirpath.ExpressionNode.Function; 036import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind; 037import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation; 038import org.hl7.fhir.r5.fhirpath.FHIRLexer.FHIRLexerException; 039import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.ClassTypeInfo; 040import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FHIRConstant; 041import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; 042import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.TypedElementDefinition; 043import org.hl7.fhir.r5.fhirpath.TypeDetails.ProfiledType; 044import org.hl7.fhir.r5.model.Base; 045import org.hl7.fhir.r5.model.BaseDateTimeType; 046import org.hl7.fhir.r5.model.BooleanType; 047import org.hl7.fhir.r5.model.CanonicalType; 048import org.hl7.fhir.r5.model.CodeType; 049import org.hl7.fhir.r5.model.CodeableConcept; 050import org.hl7.fhir.r5.model.Constants; 051import org.hl7.fhir.r5.model.DateTimeType; 052import org.hl7.fhir.r5.model.DateType; 053import org.hl7.fhir.r5.model.DecimalType; 054import org.hl7.fhir.r5.model.Element; 055import org.hl7.fhir.r5.model.ElementDefinition; 056import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 057import org.hl7.fhir.r5.model.Identifier; 058import org.hl7.fhir.r5.model.IntegerType; 059import org.hl7.fhir.r5.model.Property; 060import org.hl7.fhir.r5.model.Property.PropertyMatcher; 061import org.hl7.fhir.r5.model.Quantity; 062import org.hl7.fhir.r5.model.Resource; 063import org.hl7.fhir.r5.model.StringType; 064import org.hl7.fhir.r5.model.StructureDefinition; 065import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 066import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 067import org.hl7.fhir.r5.model.TimeType; 068import org.hl7.fhir.r5.model.TypeConvertor; 069import org.hl7.fhir.r5.model.ValueSet; 070import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 071import org.hl7.fhir.utilities.*; 072import org.hl7.fhir.utilities.MergedList.MergeNode; 073import org.hl7.fhir.utilities.fhirpath.FHIRPathConstantEvaluationMode; 074import org.hl7.fhir.utilities.i18n.I18nConstants; 075import org.hl7.fhir.utilities.validation.ValidationOptions; 076import org.hl7.fhir.utilities.xhtml.NodeType; 077import org.hl7.fhir.utilities.xhtml.XhtmlNode; 078 079import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 080import ca.uhn.fhir.util.ElementUtil; 081 082/* 083 Copyright (c) 2011+, HL7, Inc. 084 All rights reserved. 085 086 Redistribution and use in source and binary forms, with or without modification, 087 are permitted provided that the following conditions are met: 088 089 * Redistributions of source code must retain the above copyright notice, this 090 list of conditions and the following disclaimer. 091 * Redistributions in binary form must reproduce the above copyright notice, 092 this list of conditions and the following disclaimer in the documentation 093 and/or other materials provided with the distribution. 094 * Neither the name of HL7 nor the names of its contributors may be used to 095 endorse or promote products derived from this software without specific 096 prior written permission. 097 098 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 099 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 100 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 101 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 102 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 103 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 104 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 105 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 106 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 107 POSSIBILITY OF SUCH DAMAGE. 108 109 */ 110 111 112/** 113 * 114 * @author Grahame Grieve 115 * 116 */ 117@Slf4j 118public class FHIRPathEngine { 119 120 public class ExtensionDefinition { 121 122 private boolean root; 123 private StructureDefinition sd; 124 private ElementDefinition ed; 125 126 public ExtensionDefinition(boolean root, StructureDefinition sd, ElementDefinition ed) { 127 super(); 128 this.root = root; 129 this.sd = sd; 130 this.ed = ed; 131 } 132 133 public boolean isRoot() { 134 return root; 135 } 136 137 public StructureDefinition getSd() { 138 return sd; 139 } 140 141 public ElementDefinition getEd() { 142 return ed; 143 } 144 145 } 146 147 public class IssueMessage { 148 149 private String message; 150 private String id; 151 152 public IssueMessage(String message, String id) { 153 this.message = message; 154 this.id = id; 155 } 156 157 public String getMessage() { 158 return message; 159 } 160 161 public String getId() { 162 return id; 163 } 164 165 } 166 167 private enum Equality { Null, True, False } 168 169 private IWorkerContext worker; 170 private IHostApplicationServices hostServices; 171 private IDebugTracer tracer; 172 private StringBuilder traceLog = new StringBuilder(); 173 private Set<String> primitiveTypes = new HashSet<String>(); 174 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 175 private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for empty sets, so when running for R2/R3, this is set ot true 176 private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5); 177 private ProfileUtilities profileUtilities; 178 private String location; // for error messages 179 private boolean allowPolymorphicNames; 180 private boolean doImplicitStringConversion; 181 private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host 182 private boolean doNotEnforceAsSingletonRule; 183 private boolean doNotEnforceAsCaseSensitive; 184 185 /* 186 * The FHIRPath engine consults with the HostApplicationServices when an element fails to 187 * resolve, in case it's an implicit constant being referred to. it can also do that beforehand, 188 * in which case the constant will override any element of the given name. But it will only 189 * do that if CheckWithHostServicesBeforeHand is true 190 */ 191 private boolean checkWithHostServicesBeforeHand; 192 private boolean allowDoubleQuotes; 193 private List<IssueMessage> typeWarnings = new ArrayList<>(); 194 private boolean emitSQLonFHIRWarning; 195 196 public interface IDebugTracer { 197 198 /** 199 * When an expression node is evaluated during execution 200 * 201 * @param argument 202 * @param focus 203 * @return 204 */ 205 public void traceExpression(ExecutionContext context, List<Base> focus, List<Base> result, ExpressionNode exp); 206 207 /** 208 * When an Operation expression node is evaluated during execution 209 * 210 * @param argument 211 * @param focus 212 * @return 213 */ 214 public void traceOperationExpression(ExecutionContext context, List<Base> focus, List<Base> result, ExpressionNode exp); 215 } 216 217 /** 218 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 219 */ 220 public FHIRPathEngine(IWorkerContext worker) { 221 this(worker, new ProfileUtilities(worker, null, null)); 222 } 223 224 public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) { 225 super(); 226 this.worker = worker; 227 profileUtilities = utilities; 228 for (StructureDefinition sd : worker.fetchResourcesByType(StructureDefinition.class)) { 229 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { 230 allTypes.put(sd.getName(), sd); 231 } 232 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 233 primitiveTypes.add(sd.getName()); 234 } 235 } 236 initFlags(); 237 cu = new ContextUtilities(worker); 238 } 239 240 private void initFlags() { 241 if (!VersionUtilities.isR5Plus(worker.getVersion())) { 242 doNotEnforceAsCaseSensitive = true; 243 doNotEnforceAsSingletonRule = true; 244 } 245 } 246 247 // --- 3 methods to override in children ------------------------------------------------------- 248 // if you don't override, it falls through to the using the base reference implementation 249 // HAPI overrides to these to support extending the base model 250 251 public IHostApplicationServices getHostServices() { 252 return hostServices; 253 } 254 255 256 public void setHostServices(IHostApplicationServices constantResolver) { 257 this.hostServices = constantResolver; 258 } 259 260 public IDebugTracer getTracer() { 261 return tracer; 262 } 263 264 public void setTracer(IDebugTracer tracer) { 265 this.tracer = tracer; 266 } 267 268 public String getLocation() { 269 return location; 270 } 271 272 273 public void setLocation(String location) { 274 this.location = location; 275 } 276 277 278 /** 279 * Given an item, return all the children that conform to the pattern described in name 280 * 281 * Possible patterns: 282 * - a simple name (which may be the base of a name with [] e.g. value[x]) 283 * - a name with a type replacement e.g. valueCodeableConcept 284 * - * which means all children 285 * - ** which means all descendants 286 * 287 * @param item 288 * @param name 289 * @param result 290 * @throws FHIRException 291 */ 292 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 293 String tn = null; 294 if (isAllowPolymorphicNames()) { 295 // we'll look to see whether we have a polymorphic name 296 for (Property p : item.children()) { 297 if (p.getName().endsWith("[x]")) { 298 String n = p.getName().substring(0, p.getName().length()-3); 299 if (name.startsWith(n)) { 300 tn = name.substring(n.length()); 301 name = n; 302 break; 303 } 304 } 305 } 306 } 307 Base[] list = item.listChildrenByName(name, false); 308 if (list != null) { 309 for (Base v : list) { 310 if (v != null && (tn == null || v.fhirType().equalsIgnoreCase(tn))) { 311 result.add(filterIdType(v)); 312 } 313 } 314 } 315 } 316 317 private Base filterIdType(Base v) { 318 if (v instanceof IIdType) { 319 return (Base) ((IIdType) v).toUnqualifiedVersionless().withResourceType(null); 320 } 321 return v; 322 } 323 public boolean isLegacyMode() { 324 return legacyMode; 325 } 326 327 328 public void setLegacyMode(boolean legacyMode) { 329 this.legacyMode = legacyMode; 330 } 331 332 333 public boolean isDoImplicitStringConversion() { 334 return doImplicitStringConversion; 335 } 336 337 public void setDoImplicitStringConversion(boolean doImplicitStringConversion) { 338 this.doImplicitStringConversion = doImplicitStringConversion; 339 } 340 341 public boolean isDoNotEnforceAsSingletonRule() { 342 return doNotEnforceAsSingletonRule; 343 } 344 345 public void setDoNotEnforceAsSingletonRule(boolean doNotEnforceAsSingletonRule) { 346 this.doNotEnforceAsSingletonRule = doNotEnforceAsSingletonRule; 347 } 348 349 public boolean isDoNotEnforceAsCaseSensitive() { 350 return doNotEnforceAsCaseSensitive; 351 } 352 353 public void setDoNotEnforceAsCaseSensitive(boolean doNotEnforceAsCaseSensitive) { 354 this.doNotEnforceAsCaseSensitive = doNotEnforceAsCaseSensitive; 355 } 356 357 /** 358 * The FHIRPath engine consults with the HostApplicationServices when an element fails to 359 * resolve, in case it's an implicit constant being referred to. it can also do that beforehand, 360 * in which case the constant will override any element of the given name. But it will only 361 * do that if checkWithHostServicesBeforeHand is true 362 */ 363 public boolean isCheckWithHostServicesBeforeHand() { 364 return checkWithHostServicesBeforeHand; 365 } 366 367 public void setCheckWithHostServicesBeforeHand(boolean checkWithHostServicesBeforeHand) { 368 this.checkWithHostServicesBeforeHand = checkWithHostServicesBeforeHand; 369 } 370// --- public API ------------------------------------------------------- 371 /** 372 * Parse a path for later use using execute 373 * 374 * @param path 375 * @return 376 * @throws PathEngineException 377 * @throws Exception 378 */ 379 public ExpressionNode parse(String path) throws FHIRLexerException { 380 return parse(path, null); 381 } 382 383 public ExpressionNode parse(String path, String name) throws FHIRLexerException { 384 FHIRLexer lexer = new FHIRLexer(path, name, false, allowDoubleQuotes); 385 if (lexer.done()) { 386 throw lexer.error("Path cannot be empty"); 387 } 388 ExpressionNode result = parseExpression(lexer, true); 389 if (!lexer.done()) { 390 throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); 391 } 392 result.check(); 393 return result; 394 } 395 396 public static class ExpressionNodeWithOffset { 397 private int offset; 398 private ExpressionNode node; 399 public ExpressionNodeWithOffset(int offset, ExpressionNode node) { 400 super(); 401 this.offset = offset; 402 this.node = node; 403 } 404 public int getOffset() { 405 return offset; 406 } 407 public ExpressionNode getNode() { 408 return node; 409 } 410 411 } 412 /** 413 * Parse a path for later use using execute 414 * 415 * @param path 416 * @return 417 * @throws PathEngineException 418 * @throws Exception 419 */ 420 public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { 421 FHIRLexer lexer = new FHIRLexer(path, i, allowDoubleQuotes); 422 if (lexer.done()) { 423 throw lexer.error("Path cannot be empty"); 424 } 425 ExpressionNode result = parseExpression(lexer, true); 426 result.check(); 427 return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); 428 } 429 430 /** 431 * Parse a path that is part of some other syntax 432 * 433 * @return 434 * @throws PathEngineException 435 * @throws Exception 436 */ 437 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 438 ExpressionNode result = parseExpression(lexer, true); 439 result.check(); 440 return result; 441 } 442 443 /** 444 * check that paths referred to in the ExpressionNode are valid 445 * 446 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 447 * 448 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 449 * 450 * @param context - the logical type against which this path is applied 451 * @throws DefinitionException 452 * @throws PathEngineException 453 * @if the path is not valid 454 */ 455 public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 456 return check(appContext, rootResourceType, resourceType, context, expr, null); 457 } 458 459 /** 460 * check that paths referred to in the ExpressionNode are valid 461 * 462 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 463 * 464 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 465 * 466 * @param context - the logical type against which this path is applied 467 * @throws DefinitionException 468 * @throws PathEngineException 469 * @if the path is not valid 470 */ 471 public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException { 472 473 // if context is a path that refers to a type, do that conversion now 474 TypeDetails types; 475 if (context == null) { 476 types = null; // this is a special case; the first path reference will have to resolve to something in the context 477 } else if (!context.contains(".")) { 478 StructureDefinition sd = worker.fetchTypeDefinition(context); 479 if (sd == null) { 480 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context); 481 } 482 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 483 } else { 484 String ctxt = context.substring(0, context.indexOf('.')); 485 if (Utilities.isAbsoluteUrl(resourceType)) { 486 ctxt = resourceType; //.substring(0, resourceType.lastIndexOf("/")+1)+ctxt; 487 } 488 StructureDefinition sd = cu.findType(ctxt); 489 if (sd == null) { 490 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context); 491 } 492 List<ElementDefinitionMatch> edl = getElementDefinition(sd, context, true, expr); 493 if (edl.size() == 0) { 494 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); 495 } 496 if (edl.size() > 1) { 497 throw new Error("Not handled here yet"); 498 } 499 ElementDefinitionMatch ed = edl.get(0); 500 if (ed.fixedType != null) { 501 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 502 } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 503 types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context); 504 } else { 505 types = new TypeDetails(CollectionStatus.SINGLETON); 506 for (TypeRefComponent t : ed.getDefinition().getType()) { 507 types.addType(t.getCode()); 508 } 509 } 510 } 511 512 return executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, elementDependencies, true, false, expr); 513 } 514 515 /** 516 * check that paths referred to in the ExpressionNode are valid 517 * 518 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 519 * 520 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 521 * 522 * @param context - the logical type against which this path is applied 523 * @throws DefinitionException 524 * @throws PathEngineException 525 * @if the path is not valid 526 */ 527 public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, List<String> typeList, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException { 528 typeWarnings.clear(); 529 530 // if context is a path that refers to a type, do that conversion now 531 TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON); 532 for (String t : typeList) { 533 if (!t.contains(".")) { 534 StructureDefinition sd = worker.fetchTypeDefinition(t); 535 if (sd == null) { 536 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); 537 } 538 types.addType(sd.getUrl()); 539 } else { 540 boolean checkTypeName = false; 541 String ctxt = null; 542 if (t.contains("#")) { 543 ctxt = t.substring(0, t.indexOf('#')); 544 t = t.substring(t.indexOf('#')+1); 545 } else if (Utilities.isAbsoluteUrl(t)) { 546 ctxt = t; 547 t = ctxt.substring(ctxt.lastIndexOf("/")+1); 548 checkTypeName = true; 549 } else { 550 ctxt = t.substring(0, t.indexOf('.')); 551 } 552 StructureDefinition sd = cu.findType(ctxt); 553 if (sd == null) { 554 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); 555 } 556 String tn = checkTypeName ? sd.getSnapshot().getElementFirstRep().getPath() : t; 557 558 List<ElementDefinitionMatch> edl = getElementDefinition(sd, tn, true, expr); 559 if (edl.size() == 0) { 560 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t); 561 } 562 if (edl.size() > 1) { 563 throw new Error("not handled here either"); 564 } 565 ElementDefinitionMatch ed = edl.get(0); 566 if (ed.fixedType != null) { 567 types.addType(ed.fixedType); 568 } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 569 types.addType(sd.getType()+"#"+t); 570 } else { 571 for (TypeRefComponent tt : ed.getDefinition().getType()) { 572 types.addType(tt.getCode()); 573 } 574 } 575 } 576 } 577 TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, false, expr); 578 warnings.addAll(typeWarnings); 579 return res; 580 } 581 582 public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException { 583 typeWarnings.clear(); 584 TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, false, expr); 585 warnings.addAll(typeWarnings); 586 return res; 587 } 588 589 public TypeDetails checkOnTypes(Object appContext, String rootResourceType, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings, boolean canBeNone) throws FHIRLexerException, PathEngineException, DefinitionException { 590 typeWarnings.clear(); 591 TypeDetails res = executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, null, true, canBeNone, expr); 592 warnings.addAll(typeWarnings); 593 return res; 594 } 595 596 /** 597 * check that paths referred to in the ExpressionNode are valid 598 * 599 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 600 * 601 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 602 * 603 * @throws DefinitionException 604 * @throws PathEngineException 605 * @if the path is not valid 606 */ 607 public TypeDetails check(Object appContext, String rootResourceType, String resourceType, List<String> resourceTypes, ExpressionNode expr, Set<ElementDefinition> elementDependencies) throws FHIRLexerException, PathEngineException, DefinitionException { 608 609 // if context is a path that refers to a type, do that conversion now 610 TypeDetails types = null; 611 for (String rt : resourceTypes) { 612 if (types == null) { 613 types = new TypeDetails(CollectionStatus.SINGLETON, rt); 614 } else { 615 types.addType(rt); 616 } 617 } 618 619 return executeType(new ExecutionTypeContext(appContext, rootResourceType, resourceType, types, types), types, expr, elementDependencies, true, false, expr); 620 } 621 622 private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) { 623 String fmt = worker.formatMessagePlural(num, constName, args); 624 if (location != null) { 625 fmt = fmt + " "+worker.formatMessagePlural(num, I18nConstants.FHIRPATH_LOCATION, location); 626 } 627 if (holder != null) { 628 return new PathEngineException(fmt, constName, holder.getStart(), holder.toString()); 629 } else { 630 return new PathEngineException(fmt, constName); 631 } 632 } 633 634 private FHIRException makeException(ExpressionNode holder, String constName, Object... args) { 635 String fmt = worker.formatMessage(constName, args); 636 if (location != null) { 637 fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); 638 } 639 if (holder != null) { 640 return new PathEngineException(fmt, constName, holder.getStart(), holder.toString()); 641 } else { 642 return new PathEngineException(fmt, constName); 643 } 644 } 645 646 public TypeDetails check(Object appContext, String rootResourceType, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 647 // if context is a path that refers to a type, do that conversion now 648 TypeDetails types; 649 if (!context.contains(".")) { 650 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 651 } else { 652 List<ElementDefinitionMatch> edl = getElementDefinition(sd, context, true, expr); 653 if (edl.size() == 0) { 654 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context); 655 } 656 if (edl.size() > 1) { 657 throw new Error("Unhandled case?"); 658 } 659 ElementDefinitionMatch ed = edl.get(0); 660 if (ed.fixedType != null) { 661 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 662 } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 663 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); 664 } else { 665 types = new TypeDetails(CollectionStatus.SINGLETON); 666 for (TypeRefComponent t : ed.getDefinition().getType()) { 667 types.addType(t.getCode()); 668 } 669 } 670 } 671 672 return executeType(new ExecutionTypeContext(appContext, rootResourceType, sd.getUrl(), types, types), types, expr, null, true, false, expr); 673 } 674 675 public TypeDetails check(Object appContext, String rootResourceType, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 676 // if context is a path that refers to a type, do that conversion now 677 TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context 678 return executeType(new ExecutionTypeContext(appContext, rootResourceType, sd == null ? null : sd.getUrl(), null, types), types, expr, null, true, false, expr); 679 } 680 681 public TypeDetails check(Object appContext, String rootResourceType, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 682 return check(appContext, rootResourceType, resourceType, context, parse(expr)); 683 } 684 685 private Integer compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { 686 DateTimeType left = theL instanceof DateTimeType ? (DateTimeType) theL : new DateTimeType(theL.primitiveValue()); 687 DateTimeType right = theR instanceof DateTimeType ? (DateTimeType) theR : new DateTimeType(theR.primitiveValue()); 688 689 if (theEquivalenceTest) { 690 return left.equalsUsingFhirPathRules(right) == Boolean.TRUE ? 0 : 1; 691 } 692 693 if (left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 694 left.setTimeZoneZulu(true); 695 } 696 if (right.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 697 right.setTimeZoneZulu(true); 698 } 699 return BaseDateTimeType.compareTimes(left, right, null); 700 } 701 702 private Integer compareTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { 703 TimeType left = theL instanceof TimeType ? (TimeType) theL : new TimeType(theL.primitiveValue()); 704 TimeType right = theR instanceof TimeType ? (TimeType) theR : new TimeType(theR.primitiveValue()); 705 706 if (left.getHour() < right.getHour()) { 707 return -1; 708 } else if (left.getHour() > right.getHour()) { 709 return 1; 710 // hour is not a valid precision 711 // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.YEAR && dateRight.getPrecision() == TemporalPrecisionEnum.YEAR) { 712 // return 0; 713 // } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.HOUR || dateRight.getPrecision() == TemporalPrecisionEnum.HOUR) { 714 // return null; 715 } 716 717 if (left.getMinute() < right.getMinute()) { 718 return -1; 719 } else if (left.getMinute() > right.getMinute()) { 720 return 1; 721 } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE && right.getPrecision() == TemporalPrecisionEnum.MINUTE) { 722 return 0; 723 } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) { 724 return null; 725 } 726 727 if (left.getSecond() < right.getSecond()) { 728 return -1; 729 } else if (left.getSecond() > right.getSecond()) { 730 return 1; 731 } else { 732 return 0; 733 } 734 735 } 736 737 738 /** 739 * evaluate a path and return the matching elements 740 * 741 * @param base - the object against which the path is being evaluated 742 * @param ExpressionNode - the parsed ExpressionNode statement to use 743 * @return 744 * @throws FHIRException 745 * @ 746 */ 747 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { 748 List<Base> list = new ArrayList<Base>(); 749 if (base != null) { 750 list.add(base); 751 } 752 traceLog = new StringBuilder(); 753 return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); 754 } 755 756 757 /** 758 * evaluate a path and return the matching elements 759 * 760 * @param base - the object against which the path is being evaluated 761 * @param ExpressionNode - the parsed ExpressionNode statement to use 762 * @return 763 * @throws FHIRException 764 * @ 765 */ 766 public List<Base> evaluate(Object appContext, Base base, ExpressionNode ExpressionNode) throws FHIRException { 767 List<Base> list = new ArrayList<Base>(); 768 if (base != null) { 769 list.add(base); 770 } 771 traceLog = new StringBuilder(); 772 return execute(new ExecutionContext(appContext, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true); 773 } 774 775 /** 776 * evaluate a path and return the matching elements 777 * 778 * @param base - the object against which the path is being evaluated 779 * @param path - the FHIR Path statement to use 780 * @return 781 * @throws FHIRException 782 * @ 783 */ 784 public List<Base> evaluate(Base base, String path) throws FHIRException { 785 ExpressionNode exp = parse(path); 786 List<Base> list = new ArrayList<Base>(); 787 if (base != null) { 788 list.add(base); 789 } 790 traceLog = new StringBuilder(); 791 return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, base), list, exp, true); 792 } 793 794 /** 795 * evaluate a path and return the matching elements 796 * 797 * @param base - the object against which the path is being evaluated 798 * @param ExpressionNode - the parsed ExpressionNode statement to use 799 * @return 800 * @throws FHIRException 801 * @ 802 */ 803 public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 804 List<Base> list = new ArrayList<Base>(); 805 if (base != null) { 806 list.add(base); 807 } 808 traceLog = new StringBuilder(); 809 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, ExpressionNode, true); 810 } 811 812 /** 813 * evaluate a path and return the matching elements 814 * 815 * @param base - the object against which the path is being evaluated 816 * @param expressionNode - the parsed ExpressionNode statement to use 817 * @return 818 * @throws FHIRException 819 * @ 820 */ 821 public List<Base> evaluate(Object appContext, Base focusResource, Base rootResource, Base base, ExpressionNode expressionNode) throws FHIRException { 822 List<Base> list = new ArrayList<Base>(); 823 if (base != null) { 824 list.add(base); 825 } 826 traceLog = new StringBuilder(); 827 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, expressionNode, true); 828 } 829 830 /** 831 * evaluate a path and return the matching elements 832 * 833 * @param base - the object against which the path is being evaluated 834 * @param path - the FHIR Path statement to use 835 * @return 836 * @throws FHIRException 837 * @ 838 */ 839 public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { 840 ExpressionNode exp = parse(path); 841 List<Base> list = new ArrayList<Base>(); 842 if (base != null) { 843 list.add(base); 844 } 845 traceLog = new StringBuilder(); 846 return execute(new ExecutionContext(appContext, focusResource, rootResource, base, base), list, exp, true); 847 } 848 849 /** 850 * evaluate a path and return true or false (e.g. for an invariant) 851 * 852 * @param base - the object against which the path is being evaluated 853 * @param path - the FHIR Path statement to use 854 * @return 855 * @throws FHIRException 856 * @ 857 */ 858 public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException { 859 return convertToBoolean(evaluate(null, focusResource, rootResource, base, path)); 860 } 861 862 /** 863 * evaluate a path and return true or false (e.g. for an invariant) 864 * 865 * @param base - the object against which the path is being evaluated 866 * @return 867 * @throws FHIRException 868 * @ 869 */ 870 public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { 871 return convertToBoolean(evaluate(null, focusResource, rootResource, base, node)); 872 } 873 874 /** 875 * evaluate a path and return true or false (e.g. for an invariant) 876 * 877 * @param appInfo - application context 878 * @param base - the object against which the path is being evaluated 879 * @return 880 * @throws FHIRException 881 * @ 882 */ 883 public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException { 884 return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); 885 } 886 887 /** 888 * evaluate a path and return true or false (e.g. for an invariant) 889 * 890 * @param base - the object against which the path is being evaluated 891 * @return 892 * @throws FHIRException 893 * @ 894 */ 895 public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { 896 return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node)); 897 } 898 899 /** 900 * evaluate a path and a string containing the outcome (for display) 901 * 902 * @param base - the object against which the path is being evaluated 903 * @param path - the FHIR Path statement to use 904 * @return 905 * @throws FHIRException 906 * @ 907 */ 908 public String evaluateToString(Base base, String path) throws FHIRException { 909 return convertToString(evaluate(base, path)); 910 } 911 912 public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException { 913 return convertToString(evaluate(appInfo, focusResource, rootResource, base, node)); 914 } 915 916 /** 917 * worker routine for converting a set of objects to a string representation 918 * 919 * @param items - result from @evaluate 920 * @return 921 */ 922 public String convertToString(List<Base> items) { 923 StringBuilder b = new StringBuilder(); 924 boolean first = true; 925 for (Base item : items) { 926 if (first) { 927 first = false; 928 } else { 929 b.append(','); 930 } 931 932 b.append(convertToString(item)); 933 } 934 return b.toString(); 935 } 936 937 public String convertToString(Base item) { 938 if (item instanceof IIdType) { 939 return ((IIdType)item).getIdPart(); 940 } else if (item.isPrimitive()) { 941 return item.primitiveValue(); 942 } else if (item.fhirType().equals("Quantity")) { 943 Quantity q = makeQuantity(item); 944 if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds") 945 && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) { 946 return (q.hasValue() ? q.getValue().toPlainString() : "")+" "+q.getUnit(); 947 } 948 if ("http://unitsofmeasure.org".equals(q.getSystem())) { 949 String u = "'"+q.getCode()+"'"; 950 return (q.hasValue() ? q.getValue().toPlainString() : "")+" "+u; 951 } else { 952 return item.toString(); 953 } 954 } else 955 return item.toString(); 956 } 957 958 /** 959 * worker routine for converting a set of objects to a boolean representation (for invariants) 960 * 961 * @param items - result from @evaluate 962 * @return 963 */ 964 public boolean convertToBoolean(List<Base> items) { 965 if (items == null) { 966 return false; 967 } else if (items.size() == 1 && items.get(0) instanceof BooleanType) { 968 return ((BooleanType) items.get(0)).getValue(); 969 } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { // element model 970 return Boolean.valueOf(items.get(0).primitiveValue()); 971 } else { 972 return items.size() > 0; 973 } 974 } 975 976 977 private void addToLog(String name, List<Base> contents) { 978 if (hostServices == null || !hostServices.log(name, contents)) { 979 if (traceLog.length() > 0) { 980 traceLog.append("; "); 981 } 982 traceLog.append(name); 983 traceLog.append(": "); 984 boolean first = true; 985 for (Base b : contents) { 986 if (first) { 987 first = false; 988 } else { 989 traceLog.append(","); 990 } 991 traceLog.append(convertToString(b)); 992 } 993 } 994 } 995 996 public String forLog() { 997 if (traceLog.length() > 0) { 998 return " ("+ traceLog.toString()+")"; 999 } else { 1000 return ""; 1001 } 1002 } 1003 1004 public class ExecutionContext { 1005 private Object appInfo; 1006 private Base focusResource; 1007 private Base rootResource; 1008 private Base context; 1009 private Base thisItem; 1010 private List<Base> total; 1011 private int index; 1012 private Map<String, List<Base>> definedVariables; 1013 1014 public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Base thisItem) { 1015 this.appInfo = appInfo; 1016 this.context = context; 1017 this.focusResource = resource; 1018 this.rootResource = rootResource; 1019 this.thisItem = thisItem; 1020 this.index = 0; 1021 } 1022 public Base getFocusResource() { 1023 return focusResource; 1024 } 1025 public Base getRootResource() { 1026 return rootResource; 1027 } 1028 public Base getThisItem() { 1029 return thisItem; 1030 } 1031 public List<Base> getTotal() { 1032 return total; 1033 } 1034 1035 public void next() { 1036 index++; 1037 } 1038 public Base getIndex() { 1039 return new IntegerType(index); 1040 } 1041 1042 public ExecutionContext setIndex(int i) { 1043 index = i; 1044 return this; 1045 } 1046 1047 public boolean hasDefinedVariable(String name) { 1048 return definedVariables != null && definedVariables.containsKey(name); 1049 } 1050 1051 public List<Base> getDefinedVariable(String name) { 1052 return definedVariables == null ? makeNull() : definedVariables.get(name); 1053 } 1054 1055 public void setDefinedVariable(String name, List<Base> value) { 1056 if (isSystemVariable(name)) 1057 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE); 1058 1059 if (definedVariables == null) { 1060 definedVariables = new HashMap<String, List<Base>>(); 1061 } else { 1062 if (definedVariables.containsKey(name)) { 1063 // Can't do this, so throw an error 1064 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_REDEFINE_VARIABLE, name), I18nConstants.FHIRPATH_REDEFINE_VARIABLE); 1065 } 1066 } 1067 1068 definedVariables.put(name, value); 1069 } 1070 } 1071 1072 private static class ExecutionTypeContext { 1073 private Object appInfo; 1074 private String resource; 1075 private TypeDetails context; 1076 private TypeDetails thisItem; 1077 private TypeDetails total; 1078 private Map<String, TypeDetails> definedVariables; 1079 private String rootResource; 1080 1081 public ExecutionTypeContext(Object appInfo, String rootResource, String resource, TypeDetails context, TypeDetails thisItem) { 1082 super(); 1083 this.appInfo = appInfo; 1084 this.rootResource = rootResource; 1085 this.resource = resource; 1086 this.context = context; 1087 this.thisItem = thisItem; 1088 1089 } 1090 public String getResource() { 1091 return resource; 1092 } 1093 public TypeDetails getThisItem() { 1094 return thisItem; 1095 } 1096 1097 public boolean hasDefinedVariable(String name) { 1098 return definedVariables != null && definedVariables.containsKey(name); 1099 } 1100 1101 public TypeDetails getDefinedVariable(String name) { 1102 return definedVariables == null ? null : definedVariables.get(name); 1103 } 1104 1105 public void setDefinedVariable(String name, TypeDetails value) { 1106 if (isSystemVariable(name)) 1107 throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE); 1108 1109 if (definedVariables == null) { 1110 definedVariables = new HashMap<String, TypeDetails>(); 1111 } else { 1112 if (definedVariables.containsKey(name)) { 1113 // Can't do this, so throw an error 1114 throw new PathEngineException("Redefine of variable "+name, I18nConstants.FHIRPATH_REDEFINE_VARIABLE); 1115 } 1116 } 1117 1118 definedVariables.put(name, value); 1119 } 1120 } 1121 1122 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 1123 ExpressionNode result = new ExpressionNode(lexer.nextId()); 1124 ExpressionNode wrapper = null; 1125 SourceLocation c = lexer.getCurrentStartLocation().copy(); 1126 result.setStart(lexer.getCurrentStartLocation().copy()); 1127 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 1128 // so we back correct for both +/- and as part of a numeric constant below. 1129 1130 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 1131 // so we back correct for both +/- and as part of a numeric constant below. 1132 if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) { 1133 wrapper = new ExpressionNode(lexer.nextId()); 1134 wrapper.setKind(Kind.Unary); 1135 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take())); 1136 wrapper.setStart(lexer.getCurrentLocation().copy()); 1137 wrapper.setProximal(proximal); 1138 } 1139 1140 if (lexer.getCurrent() == null) { 1141 throw lexer.error("Expression terminated unexpectedly"); 1142 } else if (lexer.isConstant()) { 1143 boolean isString = lexer.isStringConstant(); 1144 if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) { 1145 // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations 1146 wrapper = new ExpressionNode(lexer.nextId()); 1147 wrapper.setKind(Kind.Unary); 1148 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1))); 1149 wrapper.setProximal(proximal); 1150 wrapper.setStart(lexer.getCurrentLocation().copy()); 1151 lexer.setCurrent(lexer.getCurrent().substring(1)); 1152 } 1153 result.setEnd(lexer.getCurrentLocation().copy()); 1154 result.setConstant(processConstant(lexer)); 1155 result.setKind(Kind.Constant); 1156 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"))) { 1157 // it's a quantity 1158 String ucum = null; 1159 String unit = null; 1160 if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { 1161 String s = lexer.take(); 1162 unit = s; 1163 if (s.equals("year") || s.equals("years")) { 1164 // this is not the UCUM year 1165 } else if (s.equals("month") || s.equals("months")) { 1166 // this is not the UCUM month 1167 } else if (s.equals("week") || s.equals("weeks")) { 1168 ucum = "wk"; 1169 } else if (s.equals("day") || s.equals("days")) { 1170 ucum = "d"; 1171 } else if (s.equals("hour") || s.equals("hours")) { 1172 ucum = "h"; 1173 } else if (s.equals("minute") || s.equals("minutes")) { 1174 ucum = "min"; 1175 } else if (s.equals("second") || s.equals("seconds")) { 1176 ucum = "s"; 1177 } else { // (s.equals("millisecond") || s.equals("milliseconds")) 1178 ucum = "ms"; 1179 } 1180 } else { 1181 result.setEnd(lexer.getCurrentLocation().copy()); 1182 ucum = lexer.readConstant("units"); 1183 } 1184 result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum)); 1185 } 1186 } else if ("(".equals(lexer.getCurrent())) { 1187 lexer.next(); 1188 result.setKind(Kind.Group); 1189 result.setGroup(parseExpression(lexer, true)); 1190 if (!")".equals(lexer.getCurrent())) { 1191 throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); 1192 } 1193 result.setEnd(lexer.getCurrentLocation().copy()); 1194 lexer.next(); 1195 } else { 1196 if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) { 1197 throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); 1198 } 1199 if (lexer.isFixedName()) { 1200 result.setEnd(lexer.getCurrentLocation().copy()); 1201 result.setName(lexer.readFixedName("Path Name")); 1202 } else { 1203 result.setEnd(lexer.getCurrentLocation().copy()); 1204 result.setName(lexer.take()); 1205 } 1206 if (!result.checkName()) { 1207 throw lexer.error("Found "+result.getName()+" expecting a valid token name"); 1208 } 1209 if ("(".equals(lexer.getCurrent())) { 1210 Function f = Function.fromCode(result.getName()); 1211 FunctionDetails details = null; 1212 if (f == null) { 1213 if (hostServices != null) { 1214 details = hostServices.resolveFunction(this, result.getName()); 1215 } 1216 if (details == null) { 1217 throw lexer.error("The name "+result.getName()+" is not a valid function name"); 1218 } 1219 f = Function.Custom; 1220 } 1221 result.setKind(Kind.Function); 1222 result.setFunction(f); 1223 lexer.next(); 1224 while (!")".equals(lexer.getCurrent())) { 1225 result.getParameters().add(parseExpression(lexer, true)); 1226 if (",".equals(lexer.getCurrent())) { 1227 lexer.next(); 1228 } else if (!")".equals(lexer.getCurrent())) { 1229 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); 1230 } 1231 } 1232 lexer.next(); 1233 checkParameters(lexer, c, result, details); 1234 } else { 1235 result.setKind(Kind.Name); 1236 } 1237 } 1238 ExpressionNode focus = result; 1239 if ("[".equals(lexer.getCurrent())) { 1240 ExpressionNode item = new ExpressionNode(lexer.nextId()); 1241 item.setKind(Kind.Function); 1242 item.setFunction(ExpressionNode.Function.Item); 1243 item.setStart(lexer.getCurrentStartLocation().copy()); 1244 lexer.next(); 1245 item.getParameters().add(parseExpression(lexer, true)); 1246 if (!lexer.getCurrent().equals("]")) { 1247 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); 1248 } 1249 item.setEnd(lexer.getCurrentLocation().copy()); 1250 lexer.next(); 1251 result.setInner(item); 1252 focus = item; 1253 } 1254 if (".".equals(lexer.getCurrent())) { 1255 lexer.next(); 1256 focus.setInner(parseExpression(lexer, false)); 1257 } 1258 result.setProximal(proximal); 1259 if (proximal) { 1260 while (lexer.isOp()) { 1261 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 1262 focus.setOpStart(lexer.getCurrentStartLocation().copy()); 1263 focus.setOpEnd(lexer.getCurrentLocation().copy()); 1264 lexer.next(); 1265 focus.setOpNext(parseExpression(lexer, false)); 1266 focus = focus.getOpNext(); 1267 } 1268 result = organisePrecedence(lexer, result); 1269 } 1270 if (wrapper != null) { 1271 wrapper.setOpNext(result); 1272 result.setProximal(false); 1273 result = wrapper; 1274 } 1275 return result; 1276 } 1277 1278 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 1279 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 1280 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 1281 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 1282 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 1283 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is, Operation.As)); 1284 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 1285 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.In, Operation.Contains, Operation.MemberOf)); 1286 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 1287 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 1288 // last: implies 1289 return node; 1290 } 1291 1292 1293 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 1294 // work : boolean; 1295 // focus, node, group : ExpressionNode; 1296 1297 assert(start.isProximal()); 1298 1299 // is there anything to do? 1300 boolean work = false; 1301 ExpressionNode focus = start.getOpNext(); 1302 if (ops.contains(start.getOperation())) { 1303 while (focus != null && focus.getOperation() != null) { 1304 work = work || !ops.contains(focus.getOperation()); 1305 focus = focus.getOpNext(); 1306 } 1307 } else { 1308 while (focus != null && focus.getOperation() != null) { 1309 work = work || ops.contains(focus.getOperation()); 1310 focus = focus.getOpNext(); 1311 } 1312 } 1313 if (!work) { 1314 return start; 1315 } 1316 1317 // entry point: tricky 1318 ExpressionNode group; 1319 if (ops.contains(start.getOperation())) { 1320 group = newGroup(lexer, start); 1321 group.setProximal(true); 1322 focus = start; 1323 start = group; 1324 } else { 1325 ExpressionNode node = start; 1326 1327 focus = node.getOpNext(); 1328 while (!ops.contains(focus.getOperation())) { 1329 node = focus; 1330 focus = focus.getOpNext(); 1331 } 1332 group = newGroup(lexer, focus); 1333 node.setOpNext(group); 1334 } 1335 1336 // now, at this point: 1337 // group is the group we are adding to, it already has a .group property filled out. 1338 // focus points at the group.group 1339 do { 1340 // run until we find the end of the sequence 1341 while (ops.contains(focus.getOperation())) { 1342 focus = focus.getOpNext(); 1343 } 1344 if (focus.getOperation() != null) { 1345 group.setOperation(focus.getOperation()); 1346 group.setOpNext(focus.getOpNext()); 1347 group.setOpStart(focus.getOpStart()); 1348 group.setOpEnd(focus.getOpEnd()); 1349 focus.setOperation(null); 1350 focus.setOpNext(null); 1351 // now look for another sequence, and start it 1352 ExpressionNode node = group; 1353 focus = group.getOpNext(); 1354 if (focus != null) { 1355 while (focus != null && !ops.contains(focus.getOperation())) { 1356 node = focus; 1357 focus = focus.getOpNext(); 1358 } 1359 if (focus != null) { // && (focus.Operation in Ops) - must be true 1360 group = newGroup(lexer, focus); 1361 node.setOpNext(group); 1362 } 1363 } 1364 } 1365 } 1366 while (focus != null && focus.getOperation() != null); 1367 return start; 1368 } 1369 1370 1371 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 1372 ExpressionNode result = new ExpressionNode(lexer.nextId()); 1373 result.setKind(Kind.Group); 1374 result.setGroup(next); 1375 result.getGroup().setProximal(true); 1376 return result; 1377 } 1378 1379 private Base processConstant(FHIRLexer lexer) throws FHIRLexerException { 1380 if (lexer.isStringConstant()) { 1381 return new StringType(processConstantString(lexer.take(), lexer)).noExtensions(); 1382 } else if (Utilities.isInteger(lexer.getCurrent())) { 1383 return new IntegerType(lexer.take()).noExtensions(); 1384 } else if (Utilities.isDecimal(lexer.getCurrent(), false)) { 1385 return new DecimalType(lexer.take()).noExtensions(); 1386 } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) { 1387 return new BooleanType(lexer.take()).noExtensions(); 1388 } else if (lexer.getCurrent().equals("{}")) { 1389 lexer.take(); 1390 return null; 1391 } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { 1392 return new FHIRConstant(lexer.take()); 1393 } else { 1394 throw lexer.error("Invalid Constant "+lexer.getCurrent()); 1395 } 1396 } 1397 1398 // procedure CheckParamCount(c : integer); 1399 // begin 1400 // if exp.Parameters.Count <> c then 1401 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 1402 // end; 1403 1404 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 1405 if (exp.getParameters().size() != count) { 1406 throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString(), location); 1407 } 1408 return true; 1409 } 1410 1411 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 1412 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) { 1413 throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString(), location); 1414 } 1415 return true; 1416 } 1417 1418 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 1419 switch (exp.getFunction()) { 1420 case Empty: return checkParamCount(lexer, location, exp, 0); 1421 case Not: return checkParamCount(lexer, location, exp, 0); 1422 case Exists: return checkParamCount(lexer, location, exp, 0, 1); 1423 case SubsetOf: return checkParamCount(lexer, location, exp, 1); 1424 case SupersetOf: return checkParamCount(lexer, location, exp, 1); 1425 case IsDistinct: return checkParamCount(lexer, location, exp, 0); 1426 case Distinct: return checkParamCount(lexer, location, exp, 0); 1427 case Count: return checkParamCount(lexer, location, exp, 0); 1428 case Where: return checkParamCount(lexer, location, exp, 1); 1429 case Select: return checkParamCount(lexer, location, exp, 1); 1430 case All: return checkParamCount(lexer, location, exp, 0, 1); 1431 case Repeat: return checkParamCount(lexer, location, exp, 1); 1432 case Aggregate: return checkParamCount(lexer, location, exp, 1, 2); 1433 case Item: return checkParamCount(lexer, location, exp, 1); 1434 case As: return checkParamCount(lexer, location, exp, 1); 1435 case OfType: return checkParamCount(lexer, location, exp, 1); 1436 case Type: return checkParamCount(lexer, location, exp, 0); 1437 case Is: return checkParamCount(lexer, location, exp, 1); 1438 case Single: return checkParamCount(lexer, location, exp, 0); 1439 case First: return checkParamCount(lexer, location, exp, 0); 1440 case Last: return checkParamCount(lexer, location, exp, 0); 1441 case Tail: return checkParamCount(lexer, location, exp, 0); 1442 case Skip: return checkParamCount(lexer, location, exp, 1); 1443 case Take: return checkParamCount(lexer, location, exp, 1); 1444 case Union: return checkParamCount(lexer, location, exp, 1); 1445 case Combine: return checkParamCount(lexer, location, exp, 1); 1446 case Intersect: return checkParamCount(lexer, location, exp, 1); 1447 case Exclude: return checkParamCount(lexer, location, exp, 1); 1448 case Iif: return checkParamCount(lexer, location, exp, 2,3); 1449 case Lower: return checkParamCount(lexer, location, exp, 0); 1450 case Upper: return checkParamCount(lexer, location, exp, 0); 1451 case ToChars: return checkParamCount(lexer, location, exp, 0); 1452 case IndexOf : return checkParamCount(lexer, location, exp, 1); 1453 case Substring: return checkParamCount(lexer, location, exp, 1, 2); 1454 case StartsWith: return checkParamCount(lexer, location, exp, 1); 1455 case EndsWith: return checkParamCount(lexer, location, exp, 1); 1456 case Matches: return checkParamCount(lexer, location, exp, 1); 1457 case MatchesFull: return checkParamCount(lexer, location, exp, 1); 1458 case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); 1459 case Contains: return checkParamCount(lexer, location, exp, 1); 1460 case Replace: return checkParamCount(lexer, location, exp, 2); 1461 case Length: return checkParamCount(lexer, location, exp, 0); 1462 case Children: return checkParamCount(lexer, location, exp, 0); 1463 case Descendants: return checkParamCount(lexer, location, exp, 0); 1464 case MemberOf: return checkParamCount(lexer, location, exp, 1); 1465 case Trace: return checkParamCount(lexer, location, exp, 1, 2); 1466 case DefineVariable: return checkParamCount(lexer, location, exp, 1, 2); 1467 case Check: return checkParamCount(lexer, location, exp, 2); 1468 case Today: return checkParamCount(lexer, location, exp, 0); 1469 case Now: return checkParamCount(lexer, location, exp, 0); 1470 case Resolve: return checkParamCount(lexer, location, exp, 0); 1471 case Extension: return checkParamCount(lexer, location, exp, 1); 1472 case AllFalse: return checkParamCount(lexer, location, exp, 0); 1473 case AnyFalse: return checkParamCount(lexer, location, exp, 0); 1474 case AllTrue: return checkParamCount(lexer, location, exp, 0); 1475 case AnyTrue: return checkParamCount(lexer, location, exp, 0); 1476 case HasValue: return checkParamCount(lexer, location, exp, 0); 1477 case Encode: return checkParamCount(lexer, location, exp, 1); 1478 case Decode: return checkParamCount(lexer, location, exp, 1); 1479 case Escape: return checkParamCount(lexer, location, exp, 1); 1480 case Unescape: return checkParamCount(lexer, location, exp, 1); 1481 case Trim: return checkParamCount(lexer, location, exp, 0); 1482 case Split: return checkParamCount(lexer, location, exp, 1); 1483 case Join: return checkParamCount(lexer, location, exp, 0, 1); 1484 case HtmlChecks1: return checkParamCount(lexer, location, exp, 0); 1485 case HtmlChecks2: return checkParamCount(lexer, location, exp, 0); 1486 case Comparable: return checkParamCount(lexer, location, exp, 1); 1487 case ToInteger: return checkParamCount(lexer, location, exp, 0); 1488 case ToDecimal: return checkParamCount(lexer, location, exp, 0); 1489 case ToString: return checkParamCount(lexer, location, exp, 0); 1490 case ToQuantity: return checkParamCount(lexer, location, exp, 0); // this has an optional unit parameter 1491 case ToBoolean: return checkParamCount(lexer, location, exp, 0); 1492 case ToDateTime: return checkParamCount(lexer, location, exp, 0); 1493 case ToTime: return checkParamCount(lexer, location, exp, 0); 1494 case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0); 1495 case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0); 1496 case ConvertsToString: return checkParamCount(lexer, location, exp, 0); 1497 case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0); // this has an optional unit parameter 1498 case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0); 1499 case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0); 1500 case ConvertsToDate: return checkParamCount(lexer, location, exp, 0); 1501 case ConvertsToTime: return checkParamCount(lexer, location, exp, 0); 1502 case ConformsTo: return checkParamCount(lexer, location, exp, 1); 1503 case Round: return checkParamCount(lexer, location, exp, 0, 1); 1504 case Sqrt: return checkParamCount(lexer, location, exp, 0); 1505 case Abs: return checkParamCount(lexer, location, exp, 0); 1506 case Ceiling: return checkParamCount(lexer, location, exp, 0); 1507 case Exp: return checkParamCount(lexer, location, exp, 0); 1508 case Floor: return checkParamCount(lexer, location, exp, 0); 1509 case Ln: return checkParamCount(lexer, location, exp, 0); 1510 case Log: return checkParamCount(lexer, location, exp, 1); 1511 case Power: return checkParamCount(lexer, location, exp, 1); 1512 case Truncate: return checkParamCount(lexer, location, exp, 0); 1513 case Sort: return checkParamCount(lexer, location, exp, 0, 10); 1514 case Coalesce: return checkParamCount(lexer, location, exp, 1, Integer.MAX_VALUE); // un-bounded number of parameters 1515 case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1); 1516 case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1); 1517 case Precision: return checkParamCount(lexer, location, exp, 0); 1518 case hasTemplateIdOf: return checkParamCount(lexer, location, exp, 1); 1519 case Debug: return checkParamCount(lexer, location, exp, 0, 1); 1520 case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 1521 } 1522 return false; 1523 } 1524 1525 private List<Base> execute(ExecutionContext inContext, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException { 1526 ExecutionContext context = contextForParameter(inContext); 1527 List<Base> work = new ArrayList<Base>(); 1528 switch (exp.getKind()) { 1529 case Unary: 1530 work.add(new IntegerType(0)); 1531 break; 1532 case Name: 1533 if (atEntry && exp.getName().equals("$this")) { 1534 work.add(context.getThisItem()); 1535 } else if (atEntry && exp.getName().equals("$total")) { 1536 work.addAll(context.getTotal()); 1537 } else if (atEntry && exp.getName().equals("$index")) { 1538 work.add(context.getIndex()); 1539 } else { 1540 for (Base item : focus) { 1541 List<Base> outcome = execute(context, item, exp, atEntry); 1542 for (Base base : outcome) { 1543 if (base != null) { 1544 work.add(base); 1545 } 1546 } 1547 } 1548 } 1549 traceExpression(focus, exp, context, work); 1550 break; 1551 case Function: 1552 List<Base> work2 = evaluateFunction(context, focus, exp); 1553 work.addAll(work2); 1554 traceExpression(focus, exp, context, work); 1555 break; 1556 case Constant: 1557 work.addAll(resolveConstant(context, exp.getConstant(), FHIRPathConstantEvaluationMode.EXPLICIT, exp)); 1558 traceExpression(focus, exp, context, work); 1559 break; 1560 case Group: 1561 work2 = execute(context, focus, exp.getGroup(), atEntry); 1562 work.addAll(work2); 1563 break; 1564 } 1565 1566 if (exp.getInner() != null) { 1567 work = execute(context, work, exp.getInner(), false); 1568 } 1569 1570 if (exp.isProximal() && exp.getOperation() != null) { 1571 ExpressionNode next = exp.getOpNext(); 1572 ExpressionNode last = exp; 1573 while (next != null) { 1574 context = contextForParameter(inContext); 1575 List<Base> work2 = preOperate(work, last.getOperation(), exp); 1576 if (work2 != null) { 1577 work = work2; 1578 } 1579 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 1580 work2 = executeTypeName(context, focus, next, false); 1581 work = operate(context, work, last.getOperation(), work2, last); 1582 if (tracer != null) { tracer.traceOperationExpression(context, focus, work, last); } 1583 } else { 1584 work2 = execute(context, focus, next, true); 1585 work = operate(context, work, last.getOperation(), work2, last); 1586 if (tracer != null) { tracer.traceOperationExpression(context, focus, work, last); } 1587 } 1588 last = next; 1589 next = next.getOpNext(); 1590 } 1591 } 1592 return work; 1593 } 1594 1595 private void traceExpression(List<Base> focus, ExpressionNode exp, ExecutionContext context, List<Base> work) { 1596 if (tracer == null) { return; } 1597 tracer.traceExpression(context, focus, work, exp); 1598 } 1599 1600 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 1601 List<Base> result = new ArrayList<Base>(); 1602 if (next.getInner() != null) { 1603 result.add(new StringType(next.getName()+"."+next.getInner().getName())); 1604 } else { 1605 result.add(new StringType(next.getName())); 1606 } 1607 return result; 1608 } 1609 1610 1611 private List<Base> preOperate(List<Base> left, Operation operation, ExpressionNode expr) throws PathEngineException { 1612 if (left.size() == 0) { 1613 return null; 1614 } 1615 switch (operation) { 1616 case And: 1617 return isBoolean(left, false) ? makeBoolean(false) : null; 1618 case Or: 1619 return isBoolean(left, true) ? makeBoolean(true) : null; 1620 case Implies: 1621 Equality v = asBool(left, expr); 1622 return v == Equality.False ? makeBoolean(true) : null; 1623 default: 1624 return null; 1625 } 1626 } 1627 1628 private List<Base> makeBoolean(boolean b) { 1629 List<Base> res = new ArrayList<Base>(); 1630 res.add(new BooleanType(b).noExtensions()); 1631 return res; 1632 } 1633 1634 public static Quantity makeQuantity(Base base) { 1635 if (base == null) { 1636 return null; 1637 } 1638 if (base instanceof Quantity) { 1639 return (Quantity) base; 1640 } 1641 if (base instanceof org.hl7.fhir.r5.elementmodel.Element) { 1642 return ObjectConverter.readAsQuantity((org.hl7.fhir.r5.elementmodel.Element) base); 1643 } 1644 return null; 1645 } 1646 1647 private List<Base> makeNull() { 1648 List<Base> res = new ArrayList<Base>(); 1649 return res; 1650 } 1651 1652 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1653 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 1654 } 1655 1656 private TypeDetails executeType(ExecutionTypeContext inContext, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, boolean atEntry, boolean canBeNone, ExpressionNode container) throws PathEngineException, DefinitionException { 1657 ExecutionTypeContext context = contextForParameter(inContext); 1658 TypeDetails result = new TypeDetails(null); 1659 switch (exp.getKind()) { 1660 case Name: 1661 if (atEntry && exp.getName().equals("$this")) { 1662 result.update(context.getThisItem()); 1663 } else if (atEntry && exp.getName().equals("$total")) { 1664 result.update(anything(CollectionStatus.UNORDERED)); 1665 } else if (atEntry && exp.getName().equals("$index")) { 1666 result.addType(TypeDetails.FP_Integer); 1667 } else if (atEntry && focus == null) { 1668 result.update(executeContextType(context, exp.getName(), exp, FHIRPathConstantEvaluationMode.NOVALUE)); 1669 } else { 1670 for (String s : focus.getTypes()) { 1671 result.update(executeType(s, exp, atEntry, focus, elementDependencies)); 1672 } 1673 if (result.hasNoTypes()) { 1674 if (!canBeNone) { 1675 throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe()); 1676 } else { 1677 // return result; 1678 } 1679 } 1680 } 1681 doSQLOnFHIRCheck(result, exp); 1682 break; 1683 case Function: 1684 result.update(evaluateFunctionType(context, focus, exp, elementDependencies, container)); 1685 break; 1686 case Unary: 1687 result.addType(TypeDetails.FP_Integer); 1688 result.addType(TypeDetails.FP_Decimal); 1689 result.addType(TypeDetails.FP_Quantity); 1690 break; 1691 case Constant: 1692 result.update(resolveConstantType(context, exp.getConstant(), exp, FHIRPathConstantEvaluationMode.EXPLICIT)); 1693 break; 1694 case Group: 1695 result.update(executeType(context, focus, exp.getGroup(), elementDependencies, atEntry, canBeNone, exp)); 1696 } 1697 exp.setTypes(result); 1698 1699 if (exp.getInner() != null) { 1700 result = executeType(context, result, exp.getInner(), elementDependencies, false, false, exp); 1701 } 1702 1703 if (exp.isProximal() && exp.getOperation() != null) { 1704 ExpressionNode next = exp.getOpNext(); 1705 ExpressionNode last = exp; 1706 while (next != null) { 1707 context = contextForParameter(inContext); 1708 TypeDetails work; 1709 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 1710 work = executeTypeName(context, focus, next, atEntry); 1711 } else { 1712 work = executeType(context, focus, next, elementDependencies, atEntry, canBeNone, exp); 1713 } 1714 result = operateTypes(result, last.getOperation(), work, last); 1715 last = next; 1716 next = next.getOpNext(); 1717 } 1718 exp.setOpTypes(result); 1719 } 1720 return result; 1721 } 1722 1723 private void doSQLOnFHIRCheck(TypeDetails focus, ExpressionNode expr) { 1724 if (emitSQLonFHIRWarning) { 1725 // special Logic for SQL-on-FHIR: 1726 if (focus.isChoice()) { 1727 if (expr.getInner() == null || expr.getInner().getFunction() != Function.OfType) { 1728 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER)); 1729 } 1730 } else if (expr.getInner() != null && expr.getInner().getFunction() == Function.OfType) { 1731 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER)); 1732 } 1733 } 1734 } 1735 private List<Base> resolveConstant(ExecutionContext context, Base constant, FHIRPathConstantEvaluationMode mode, ExpressionNode expr) throws PathEngineException { 1736 if (constant == null) { 1737 return new ArrayList<Base>(); 1738 } 1739 if (!(constant instanceof FHIRConstant)) { 1740 return new ArrayList<Base>(Arrays.asList(constant)); 1741 } 1742 FHIRConstant c = (FHIRConstant) constant; 1743 if (c.getValue().startsWith("%")) { 1744 String varName = c.getValue().substring(1); 1745 if (context.hasDefinedVariable(varName)) { 1746 return context.getDefinedVariable(varName); 1747 } 1748 return resolveConstant(context, c.getValue(), mode, expr); 1749 } else if (c.getValue().startsWith("@")) { 1750 return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr))); 1751 } else { 1752 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, c.getValue()); 1753 } 1754 } 1755 1756 private Base processDateConstant(Object appInfo, String value, ExpressionNode expr) throws PathEngineException { 1757 String date = null; 1758 String time = null; 1759 String tz = null; 1760 1761 TemporalPrecisionEnum temp = null; 1762 1763 if (value.startsWith("T")) { 1764 time = value.substring(1); 1765 } else if (!value.contains("T")) { 1766 date = value; 1767 } else { 1768 String[] p = value.split("T"); 1769 date = p[0]; 1770 if (p.length > 1) { 1771 time = p[1]; 1772 } 1773 } 1774 1775 if (time != null) { 1776 int i = time.indexOf("-"); 1777 if (i == -1) { 1778 i = time.indexOf("+"); 1779 } 1780 if (i == -1) { 1781 i = time.indexOf("Z"); 1782 } 1783 if (i > -1) { 1784 tz = time.substring(i); 1785 time = time.substring(0, i); 1786 } 1787 1788 if (time.length() == 2) { 1789 time = time+":00:00"; 1790 temp = TemporalPrecisionEnum.MINUTE; 1791 } else if (time.length() == 5) { 1792 temp = TemporalPrecisionEnum.MINUTE; 1793 time = time+":00"; 1794 } else if (time.contains(".")) { 1795 temp = TemporalPrecisionEnum.MILLI; 1796 } else { 1797 temp = TemporalPrecisionEnum.SECOND; 1798 } 1799 } 1800 1801 if (date == null) { 1802 if (tz != null) { 1803 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, value); 1804 } else { 1805 TimeType tt = new TimeType(time); 1806 tt.setPrecision(temp); 1807 return tt.noExtensions(); 1808 } 1809 } else if (time != null) { 1810 DateTimeType dt = new DateTimeType(date+"T"+time+(tz == null ? "" : tz)); 1811 dt.setPrecision(temp); 1812 return dt.noExtensions(); 1813 } else { 1814 return new DateType(date).noExtensions(); 1815 } 1816 } 1817 1818 static boolean isSystemVariable(String name){ 1819 if (name.equals("sct")) 1820 return true; 1821 if (name.equals("loinc")) 1822 return true; 1823 if (name.equals("ucum")) 1824 return true; 1825 if (name.equals("resource")) 1826 return true; 1827 if (name.equals("rootResource")) 1828 return true; 1829 if (name.equals("context")) 1830 return true; 1831 return false; 1832 } 1833 1834 private List<Base> resolveConstant(ExecutionContext context, String s, FHIRPathConstantEvaluationMode mode, ExpressionNode expr) throws PathEngineException { 1835 if (s.equals("%sct")) { 1836 return new ArrayList<Base>(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions())); 1837 } else if (s.equals("%loinc")) { 1838 return new ArrayList<Base>(Arrays.asList(new StringType("http://loinc.org").noExtensions())); 1839 } else if (s.equals("%ucum")) { 1840 return new ArrayList<Base>(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions())); 1841 } else if (s.equals("%resource")) { 1842 if (context.focusResource == null) { 1843 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource"); 1844 } 1845 return new ArrayList<Base>(Arrays.asList(context.focusResource)); 1846 } else if (s.equals("%rootResource")) { 1847 if (context.rootResource == null) { 1848 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource"); 1849 } 1850 return new ArrayList<Base>(Arrays.asList(context.rootResource)); 1851 } else if (s.equals("%context")) { 1852 return new ArrayList<Base>(Arrays.asList(context.context)); 1853 } else if (s.equals("%us-zip")) { 1854 return new ArrayList<Base>(Arrays.asList(new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions())); 1855 } else if (s.startsWith("%`vs-")) { 1856 return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions())); 1857 } else if (s.startsWith("%`cs-")) { 1858 return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions())); 1859 } else if (s.startsWith("%`ext-")) { 1860 return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions())); 1861 } else if (hostServices == null) { 1862 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 1863 } else { 1864 return hostServices.resolveConstant(this, context.appInfo, mode == FHIRPathConstantEvaluationMode.EXPLICIT ? s.substring(1) : s, mode); 1865 } 1866 } 1867 1868 1869 private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException { 1870 StringBuilder b = new StringBuilder(); 1871 int i = 1; 1872 while (i < s.length()-1) { 1873 char ch = s.charAt(i); 1874 if (ch == '\\') { 1875 i++; 1876 switch (s.charAt(i)) { 1877 case 't': 1878 b.append('\t'); 1879 break; 1880 case 'r': 1881 b.append('\r'); 1882 break; 1883 case 'n': 1884 b.append('\n'); 1885 break; 1886 case 'f': 1887 b.append('\f'); 1888 break; 1889 case '\'': 1890 b.append('\''); 1891 break; 1892 case '"': 1893 b.append('"'); 1894 break; 1895 case '`': 1896 b.append('`'); 1897 break; 1898 case '\\': 1899 b.append('\\'); 1900 break; 1901 case '/': 1902 b.append('/'); 1903 break; 1904 case 'u': 1905 i++; 1906 int uc = Integer.parseInt(s.substring(i, i+4), 16); 1907 b.append(Character.toString(uc)); 1908 i = i + 3; 1909 break; 1910 default: 1911 throw lexer.error("Unknown FHIRPath character escape \\"+s.charAt(i)); 1912 } 1913 i++; 1914 } else { 1915 b.append(ch); 1916 i++; 1917 } 1918 } 1919 return b.toString(); 1920 } 1921 1922 1923 private List<Base> operate(ExecutionContext context, List<Base> left, Operation operation, List<Base> right, ExpressionNode holder) throws FHIRException { 1924 switch (operation) { 1925 case Equals: return opEquals(left, right, holder); 1926 case Equivalent: return opEquivalent(left, right, holder); 1927 case NotEquals: return opNotEquals(left, right, holder); 1928 case NotEquivalent: return opNotEquivalent(left, right, holder); 1929 case LessThan: return opLessThan(left, right, holder); 1930 case Greater: return opGreater(left, right, holder); 1931 case LessOrEqual: return opLessOrEqual(left, right, holder); 1932 case GreaterOrEqual: return opGreaterOrEqual(left, right, holder); 1933 case Union: return opUnion(left, right, holder); 1934 case In: return opIn(left, right, holder); 1935 case MemberOf: return opMemberOf(context, left, right, holder); 1936 case Contains: return opContains(left, right, holder); 1937 case Or: return opOr(left, right, holder); 1938 case And: return opAnd(left, right, holder); 1939 case Xor: return opXor(left, right, holder); 1940 case Implies: return opImplies(left, right, holder); 1941 case Plus: return opPlus(left, right, holder); 1942 case Times: return opTimes(left, right, holder); 1943 case Minus: return opMinus(left, right, holder); 1944 case Concatenate: return opConcatenate(left, right, holder); 1945 case DivideBy: return opDivideBy(left, right, holder); 1946 case Div: return opDiv(left, right, holder); 1947 case Mod: return opMod(left, right, holder); 1948 case Is: return opIs(left, right, holder); 1949 case As: return opAs(left, right, holder); 1950 default: 1951 throw new Error("Not Done Yet: "+operation.toCode()); 1952 } 1953 } 1954 1955 private List<Base> opAs(List<Base> left, List<Base> right, ExpressionNode expr) { 1956 List<Base> result = new ArrayList<>(); 1957 if (right.size() != 1) { 1958 return result; 1959 } else { 1960 String tn = convertToString(right); 1961 if (!isKnownType(tn)) { 1962 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 1963 } 1964 if (!doNotEnforceAsSingletonRule && left.size() > 1) { 1965 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, left.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 1966 } 1967 for (Base nextLeft : left) { 1968 if (compareTypeNames(tn, nextLeft.fhirType())) { 1969 result.add(nextLeft); 1970 } 1971 } 1972 } 1973 return result; 1974 } 1975 1976 private boolean compareTypeNames(String left, String right) { 1977 if (doNotEnforceAsCaseSensitive) { 1978 return left.equalsIgnoreCase(right); 1979 } else { 1980 return left.equals(right); 1981 } 1982 } 1983 1984 private boolean isKnownType(String tn) { 1985 if (!tn.contains(".")) { 1986 if (Utilities.existsInList(tn, "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { 1987 return true; 1988 } 1989 try { 1990 return worker.fetchTypeDefinition(tn) != null; 1991 } catch (Exception e) { 1992 return false; 1993 } 1994 } 1995 String[] t = tn.split("\\."); 1996 if (t.length != 2) { 1997 return false; 1998 } 1999 if ("System".equals(t[0])) { 2000 return Utilities.existsInList(t[1], "String", "Boolean", "Integer", "Decimal", "Quantity", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo"); 2001 } else if ("FHIR".equals(t[0])) { 2002 try { 2003 return worker.fetchTypeDefinition(t[1]) != null; 2004 } catch (Exception e) { 2005 return false; 2006 } 2007 } else if ("CDA".equals(t[0])) { 2008 try { 2009 return worker.fetchTypeDefinition(Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", t[1])) != null; 2010 } catch (Exception e) { 2011 return false; 2012 } 2013 } else { 2014 return false; 2015 } 2016 } 2017 2018 private List<Base> opIs(List<Base> left, List<Base> right, ExpressionNode expr) { 2019 List<Base> result = new ArrayList<Base>(); 2020 if (left.size() == 0 || right.size() == 0) { 2021 } else if (left.size() != 1 || right.size() != 1) 2022 result.add(new BooleanType(false).noExtensions()); 2023 else { 2024 String tn = convertToString(right); 2025 var leftValue = left.get(0); 2026 var leftFhirType = leftValue.fhirType(); 2027 if (leftValue instanceof org.hl7.fhir.r5.elementmodel.Element) { 2028 // Need to handle the fhir type inheritance here 2029 if (leftFhirType.equals(tn)) { 2030 result.add(new BooleanType(true).noExtensions()); 2031 } else { 2032 StructureDefinition sd = worker.fetchTypeDefinition(leftFhirType); 2033 while (sd != null) { 2034 if (tn.equals(sd.getType())) { 2035 return makeBoolean(true); 2036 } 2037 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 2038 } 2039 return makeBoolean(false); 2040 } 2041 } else if ((leftValue instanceof Element) && ((Element) leftValue).isDisallowExtensions()) { 2042 result.add(new BooleanType(Utilities.capitalize(leftFhirType).equals(tn) || ("System."+Utilities.capitalize(leftFhirType)).equals(tn)).noExtensions()); 2043 } else { 2044 if (leftFhirType.equals(tn)) { 2045 result.add(new BooleanType(true).noExtensions()); 2046 } else { 2047 StructureDefinition sd = worker.fetchTypeDefinition(leftFhirType); 2048 while (sd != null) { 2049 if (tn.equals(sd.getType())) { 2050 return makeBoolean(true); 2051 } 2052 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 2053 } 2054 return makeBoolean(false); 2055 } 2056 } 2057 } 2058 return result; 2059 } 2060 2061 2062 private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { 2063 if (left.isList() && !right.isList()) { 2064 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT)); 2065 } else if (!left.isList() && right.isList()) { 2066 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT)); 2067 } 2068 } 2069 2070 private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { 2071 if (left.isList()) { 2072 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT)); 2073 } 2074 if (right.isList()) { 2075 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT)); 2076 } 2077 } 2078 2079 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { 2080 switch (operation) { 2081 case Equals: 2082 checkCardinalityForComparabilitySame(left, operation, right, expr); 2083 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2084 case Equivalent: 2085 checkCardinalityForComparabilitySame(left, operation, right, expr); 2086 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2087 case NotEquals: 2088 checkCardinalityForComparabilitySame(left, operation, right, expr); 2089 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2090 case NotEquivalent: 2091 checkCardinalityForComparabilitySame(left, operation, right, expr); 2092 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2093 case LessThan: 2094 checkCardinalityForSingle(left, operation, right, expr); 2095 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2096 case Greater: 2097 checkCardinalityForSingle(left, operation, right, expr); 2098 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2099 case LessOrEqual: 2100 checkCardinalityForSingle(left, operation, right, expr); 2101 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2102 case GreaterOrEqual: 2103 checkCardinalityForSingle(left, operation, right, expr); 2104 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2105 case Is: 2106 checkCardinalityForSingle(left, operation, right, expr); 2107 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2108 case As: 2109 checkCardinalityForSingle(left, operation, right, expr); 2110 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 2111 if (td.typesHaveTargets()) { 2112 td.addTargets(left.getTargets()); 2113 } 2114 return td; 2115 case Union: return left.union(right); 2116 case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2117 case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2118 case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2119 case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2120 case Times: 2121 checkCardinalityForSingle(left, operation, right, expr); 2122 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 2123 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { 2124 result.addType(TypeDetails.FP_Integer); 2125 } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 2126 result.addType(TypeDetails.FP_Decimal); 2127 } 2128 return result; 2129 case DivideBy: 2130 checkCardinalityForSingle(left, operation, right, expr); 2131 result = new TypeDetails(CollectionStatus.SINGLETON); 2132 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { 2133 result.addType(TypeDetails.FP_Decimal); 2134 } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 2135 result.addType(TypeDetails.FP_Decimal); 2136 } 2137 return result; 2138 case Concatenate: 2139 checkCardinalityForSingle(left, operation, right, expr); 2140 result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2141 return result; 2142 case Plus: 2143 checkCardinalityForSingle(left, operation, right, expr); 2144 result = new TypeDetails(CollectionStatus.SINGLETON); 2145 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { 2146 result.addType(TypeDetails.FP_Integer); 2147 } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 2148 result.addType(TypeDetails.FP_Decimal); 2149 } else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) { 2150 result.addType(TypeDetails.FP_String); 2151 } else if (left.hasType(worker, "date", "dateTime", "instant")) { 2152 if (right.hasType(worker, "Quantity")) { 2153 result.addType(left.getType()); 2154 } else { 2155 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_PLUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_PLUS, expr.getOpStart(), expr.toString()); 2156 } 2157 } 2158 return result; 2159 case Minus: 2160 checkCardinalityForSingle(left, operation, right, expr); 2161 result = new TypeDetails(CollectionStatus.SINGLETON); 2162 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { 2163 result.addType(TypeDetails.FP_Integer); 2164 } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 2165 result.addType(TypeDetails.FP_Decimal); 2166 } else if (left.hasType(worker, "Quantity") && right.hasType(worker, "Quantity")) { 2167 result.addType(TypeDetails.FP_Quantity); 2168 } else if (left.hasType(worker, "date", "dateTime", "instant")) { 2169 if (right.hasType(worker, "Quantity")) { 2170 result.addType(left.getType()); 2171 } else { 2172 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_MINUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_MINUS, expr.getOpStart(), expr.toString()); 2173 } 2174 } 2175 return result; 2176 case Div: 2177 case Mod: 2178 checkCardinalityForSingle(left, operation, right, expr); 2179 result = new TypeDetails(CollectionStatus.SINGLETON); 2180 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) { 2181 result.addType(TypeDetails.FP_Integer); 2182 } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) { 2183 result.addType(TypeDetails.FP_Decimal); 2184 } 2185 return result; 2186 case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2187 case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2188 case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2189 default: 2190 return null; 2191 } 2192 } 2193 2194 2195 private List<Base> opEquals(List<Base> left, List<Base> right, ExpressionNode expr) { 2196 if (left.size() == 0 || right.size() == 0) { 2197 return new ArrayList<Base>(); 2198 } 2199 2200 if (left.size() != right.size()) { 2201 return makeBoolean(false); 2202 } 2203 2204 boolean res = true; 2205 boolean nil = false; 2206 for (int i = 0; i < left.size(); i++) { 2207 Boolean eq = doEquals(left.get(i), right.get(i)); 2208 if (eq == null) { 2209 nil = true; 2210 } else if (eq == false) { 2211 res = false; 2212 break; 2213 } 2214 } 2215 if (!res) { 2216 return makeBoolean(res); 2217 } else if (nil) { 2218 return new ArrayList<Base>(); 2219 } else { 2220 return makeBoolean(res); 2221 } 2222 } 2223 2224 private List<Base> opNotEquals(List<Base> left, List<Base> right, ExpressionNode expr) { 2225 if (!legacyMode && (left.size() == 0 || right.size() == 0)) { 2226 return new ArrayList<Base>(); 2227 } 2228 2229 if (left.size() != right.size()) { 2230 return makeBoolean(true); 2231 } 2232 2233 boolean res = true; 2234 boolean nil = false; 2235 for (int i = 0; i < left.size(); i++) { 2236 Boolean eq = doEquals(left.get(i), right.get(i)); 2237 if (eq == null) { 2238 nil = true; 2239 } else if (eq == true) { 2240 res = false; 2241 break; 2242 } 2243 } 2244 if (!res) { 2245 return makeBoolean(res); 2246 } else if (nil) { 2247 return new ArrayList<Base>(); 2248 } else { 2249 return makeBoolean(res); 2250 } 2251 } 2252 2253 private String removeTrailingZeros(String s) { 2254 if (Utilities.noString(s)) 2255 return ""; 2256 int i = s.length()-1; 2257 boolean done = false; 2258 boolean dot = false; 2259 while (i > 0 && !done) { 2260 if (s.charAt(i) == '.') { 2261 i--; 2262 dot = true; 2263 } else if (!dot && s.charAt(i) == '0') { 2264 i--; 2265 } else { 2266 done = true; 2267 } 2268 } 2269 return s.substring(0, i+1); 2270 } 2271 2272 private boolean decEqual(String left, String right) { 2273 left = removeTrailingZeros(left); 2274 right = removeTrailingZeros(right); 2275 return left.equals(right); 2276 } 2277 2278 private Boolean datesEqual(BaseDateTimeType left, BaseDateTimeType right) { 2279 return left.equalsUsingFhirPathRules(right); 2280 } 2281 2282 private Boolean doEquals(Base left, Base right) { 2283 var lq = makeQuantity(left); 2284 var rq = makeQuantity(right); 2285 if (lq instanceof Quantity && rq instanceof Quantity) { 2286 return qtyEqual(lq, rq); 2287 } else if (left.hasType("date", "dateTime", "instant") && right.hasType("date", "dateTime", "instant")) { 2288 var leftDate = new DateTimeType(left.primitiveValue()); 2289 var rightDate = new DateTimeType(right.primitiveValue()); 2290 return datesEqual(leftDate, rightDate); 2291 } else if (left instanceof DecimalType || right instanceof DecimalType) { 2292 return decEqual(left.primitiveValue(), right.primitiveValue()); 2293 } else if (left.isPrimitive() && right.isPrimitive()) { 2294 return Base.equals(left.primitiveValue(), right.primitiveValue()); 2295 } else { 2296 return Base.compareDeep(left, right, false); 2297 } 2298 } 2299 2300 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 2301 var lq = makeQuantity(left); 2302 var rq = makeQuantity(right); 2303 if (lq instanceof Quantity && rq instanceof Quantity) { 2304 return qtyEquivalent(lq, rq); 2305 } 2306 if (left.hasType("integer") && right.hasType("integer")) { 2307 return doEquals(left, right); 2308 } 2309 if (left.hasType("boolean") && right.hasType("boolean")) { 2310 return doEquals(left, right); 2311 } 2312 if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 2313 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 2314 } 2315 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) { 2316 Integer i = compareDateTimeElements(left, right, true); 2317 if (i == null) { 2318 i = 0; 2319 } 2320 return i == 0; 2321 } 2322 if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) { 2323 return Utilities.equivalent(convertToString(left), convertToString(right)); 2324 } 2325 if (left.isPrimitive() && right.isPrimitive()) { 2326 return Utilities.equivalent(left.primitiveValue(), right.primitiveValue()); 2327 } 2328 if (!left.isPrimitive() && !right.isPrimitive()) { 2329 MergedList<Property> props = new MergedList<Property>(left.children(), right.children(), new PropertyMatcher()); 2330 for (MergeNode<Property> t : props) { 2331 if (t.hasLeft() && t.hasRight()) { 2332 if (t.getLeft().hasValues() && t.getRight().hasValues()) { 2333 MergedList<Base> values = new MergedList<Base>(t.getLeft().getValues(), t.getRight().getValues()); 2334 for (MergeNode<Base> v : values) { 2335 if (v.hasLeft() && v.hasRight()) { 2336 if (!doEquivalent(v.getLeft(), v.getRight())) { 2337 return false; 2338 } 2339 } else if (v.hasLeft() || v.hasRight()) { 2340 return false; 2341 } 2342 } 2343 } else if (t.getLeft().hasValues() || t.getRight().hasValues()) { 2344 return false; 2345 } 2346 } else { 2347 return false; 2348 } 2349 } 2350 return true; 2351 } else { 2352 return false; 2353 } 2354 } 2355 2356 private Boolean qtyEqual(Quantity left, Quantity right) { 2357 if (!left.hasValue() && !right.hasValue()) { 2358 return true; 2359 } 2360 if (!left.hasValue() || !right.hasValue()) { 2361 return null; 2362 } 2363 if (worker.getUcumService() != null) { 2364 Pair dl = qtyToCanonicalPair(left); 2365 Pair dr = qtyToCanonicalPair(right); 2366 if (dl != null && dr != null) { 2367 if (dl.getCode().equals(dr.getCode())) { 2368 return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); 2369 } else { 2370 return false; 2371 } 2372 } 2373 } 2374 if (left.hasCode() || right.hasCode()) { 2375 if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { 2376 return null; 2377 } 2378 } else if (!left.hasUnit() || right.hasUnit()) { 2379 if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { 2380 return null; 2381 } 2382 } 2383 return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue())); 2384 } 2385 2386 private Pair qtyToCanonicalPair(Quantity q) { 2387 if (!"http://unitsofmeasure.org".equals(q.getSystem())) { 2388 return null; 2389 } 2390 try { 2391 Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode()); 2392 Pair c = worker.getUcumService().getCanonicalForm(p); 2393 return c; 2394 } catch (UcumException e) { 2395 return null; 2396 } 2397 } 2398 2399 private DecimalType qtyToCanonicalDecimal(Quantity q) { 2400 if (!"http://unitsofmeasure.org".equals(q.getSystem())) { 2401 return null; 2402 } 2403 try { 2404 Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode()); 2405 Pair c = worker.getUcumService().getCanonicalForm(p); 2406 return new DecimalType(c.getValue().asDecimal()); 2407 } catch (UcumException e) { 2408 return null; 2409 } 2410 } 2411 2412 private Base pairToQty(Pair p) { 2413 return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions(); 2414 } 2415 2416 2417 private Pair qtyToPair(Quantity q) { 2418 if (!"http://unitsofmeasure.org".equals(q.getSystem())) { 2419 return null; 2420 } 2421 try { 2422 return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 2423 } catch (UcumException e) { 2424 return null; 2425 } 2426 } 2427 2428 2429 private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { 2430 if (!left.hasValue() && !right.hasValue()) { 2431 return true; 2432 } 2433 if (!left.hasValue() || !right.hasValue()) { 2434 return null; 2435 } 2436 if (worker.getUcumService() != null) { 2437 Pair dl = qtyToCanonicalPair(left); 2438 Pair dr = qtyToCanonicalPair(right); 2439 if (dl != null && dr != null) { 2440 if (dl.getCode().equals(dr.getCode())) { 2441 return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal())); 2442 } else { 2443 return false; 2444 } 2445 } 2446 } 2447 if (left.hasCode() || right.hasCode()) { 2448 if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) { 2449 return null; 2450 } 2451 } else if (!left.hasUnit() || right.hasUnit()) { 2452 if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) { 2453 return null; 2454 } 2455 } 2456 return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue())); 2457 } 2458 2459 2460 2461 private List<Base> opEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2462 if (left.size() != right.size()) { 2463 return makeBoolean(false); 2464 } 2465 2466 boolean res = true; 2467 for (int i = 0; i < left.size(); i++) { 2468 boolean found = false; 2469 for (int j = 0; j < right.size(); j++) { 2470 if (doEquivalent(left.get(i), right.get(j))) { 2471 found = true; 2472 break; 2473 } 2474 } 2475 if (!found) { 2476 res = false; 2477 break; 2478 } 2479 } 2480 return makeBoolean(res); 2481 } 2482 2483 private List<Base> opNotEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2484 if (left.size() != right.size()) { 2485 return makeBoolean(true); 2486 } 2487 2488 boolean res = true; 2489 for (int i = 0; i < left.size(); i++) { 2490 boolean found = false; 2491 for (int j = 0; j < right.size(); j++) { 2492 if (doEquivalent(left.get(i), right.get(j))) { 2493 found = true; 2494 break; 2495 } 2496 } 2497 if (!found) { 2498 res = false; 2499 break; 2500 } 2501 } 2502 return makeBoolean(!res); 2503 } 2504 2505 private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url", "xhtml"}; 2506 2507 private List<Base> opLessThan(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2508 if (left.size() == 0 || right.size() == 0) 2509 return new ArrayList<Base>(); 2510 2511 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2512 Base l = left.get(0); 2513 Base r = right.get(0); 2514 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 2515 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 2516 } else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) { 2517 return makeBoolean(Double.valueOf(l.primitiveValue()) < Double.valueOf(r.primitiveValue())); 2518 } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { 2519 Integer i = compareDateTimeElements(l, r, false); 2520 if (i == null) { 2521 return makeNull(); 2522 } else { 2523 return makeBoolean(i < 0); 2524 } 2525 } else if ((l.hasType("time")) && (r.hasType("time"))) { 2526 Integer i = compareTimeElements(l, r, false); 2527 if (i == null) { 2528 return makeNull(); 2529 } else { 2530 return makeBoolean(i < 0); 2531 } 2532 } else { 2533 throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); 2534 } 2535 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 2536 List<Base> lUnit = left.get(0).listChildrenByName("code"); 2537 List<Base> rUnit = right.get(0).listChildrenByName("code"); 2538 if (Base.compareDeep(lUnit, rUnit, true)) { 2539 return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); 2540 } else { 2541 if (worker.getUcumService() == null) { 2542 return makeBoolean(false); 2543 } else { 2544 var lq = makeQuantity(left.get(0)); 2545 var rq = makeQuantity(right.get(0)); 2546 List<Base> dl = new ArrayList<Base>(); 2547 dl.add(qtyToCanonicalDecimal(lq)); 2548 List<Base> dr = new ArrayList<Base>(); 2549 dr.add(qtyToCanonicalDecimal(rq)); 2550 return opLessThan(dl, dr, expr); 2551 } 2552 } 2553 } 2554 return new ArrayList<Base>(); 2555 } 2556 2557 private List<Base> opGreater(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2558 if (left.size() == 0 || right.size() == 0) 2559 return new ArrayList<Base>(); 2560 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2561 Base l = left.get(0); 2562 Base r = right.get(0); 2563 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 2564 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 2565 } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 2566 return makeBoolean(Double.valueOf(l.primitiveValue()) > Double.valueOf(r.primitiveValue())); 2567 } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { 2568 Integer i = compareDateTimeElements(l, r, false); 2569 if (i == null) { 2570 return makeNull(); 2571 } else { 2572 return makeBoolean(i > 0); 2573 } 2574 } else if ((l.hasType("time")) && (r.hasType("time"))) { 2575 Integer i = compareTimeElements(l, r, false); 2576 if (i == null) { 2577 return makeNull(); 2578 } else { 2579 return makeBoolean(i > 0); 2580 } 2581 } else { 2582 throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); 2583 } 2584 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 2585 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 2586 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 2587 if (Base.compareDeep(lUnit, rUnit, true)) { 2588 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); 2589 } else { 2590 if (worker.getUcumService() == null) { 2591 return makeBoolean(false); 2592 } else { 2593 var lq = makeQuantity(left.get(0)); 2594 var rq = makeQuantity(right.get(0)); 2595 List<Base> dl = new ArrayList<Base>(); 2596 dl.add(qtyToCanonicalDecimal(lq)); 2597 List<Base> dr = new ArrayList<Base>(); 2598 dr.add(qtyToCanonicalDecimal(rq)); 2599 return opGreater(dl, dr, expr); 2600 } 2601 } 2602 } 2603 return new ArrayList<Base>(); 2604 } 2605 2606 private List<Base> opLessOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2607 if (left.size() == 0 || right.size() == 0) { 2608 return new ArrayList<Base>(); 2609 } 2610 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2611 Base l = left.get(0); 2612 Base r = right.get(0); 2613 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 2614 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 2615 } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 2616 return makeBoolean(Double.valueOf(l.primitiveValue()) <= Double.valueOf(r.primitiveValue())); 2617 } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { 2618 Integer i = compareDateTimeElements(l, r, false); 2619 if (i == null) { 2620 return makeNull(); 2621 } else { 2622 return makeBoolean(i <= 0); 2623 } 2624 } else if ((l.hasType("time")) && (r.hasType("time"))) { 2625 Integer i = compareTimeElements(l, r, false); 2626 if (i == null) { 2627 return makeNull(); 2628 } else { 2629 return makeBoolean(i <= 0); 2630 } 2631 } else { 2632 throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); 2633 } 2634 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 2635 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 2636 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 2637 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 2638 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 2639 if ((lunit == null && runit == null) || lunit.equals(runit)) { 2640 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); 2641 } else { 2642 if (worker.getUcumService() == null) { 2643 return makeBoolean(false); 2644 } else { 2645 var lq = makeQuantity(left.get(0)); 2646 var rq = makeQuantity(right.get(0)); 2647 List<Base> dl = new ArrayList<Base>(); 2648 dl.add(qtyToCanonicalDecimal(lq)); 2649 List<Base> dr = new ArrayList<Base>(); 2650 dr.add(qtyToCanonicalDecimal(rq)); 2651 return opLessOrEqual(dl, dr, expr); 2652 } 2653 } 2654 } 2655 return new ArrayList<Base>(); 2656 } 2657 2658 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2659 if (left.size() == 0 || right.size() == 0) { 2660 return new ArrayList<Base>(); 2661 } 2662 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 2663 Base l = left.get(0); 2664 Base r = right.get(0); 2665 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 2666 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 2667 } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 2668 return makeBoolean(Double.valueOf(l.primitiveValue()) >= Double.valueOf(r.primitiveValue())); 2669 } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) { 2670 Integer i = compareDateTimeElements(l, r, false); 2671 if (i == null) { 2672 return makeNull(); 2673 } else { 2674 return makeBoolean(i >= 0); 2675 } 2676 } else if ((l.hasType("time")) && (r.hasType("time"))) { 2677 Integer i = compareTimeElements(l, r, false); 2678 if (i == null) { 2679 return makeNull(); 2680 } else { 2681 return makeBoolean(i >= 0); 2682 } 2683 } else { 2684 throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType()); 2685 } 2686 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 2687 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 2688 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 2689 if (Base.compareDeep(lUnit, rUnit, true)) { 2690 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr); 2691 } else { 2692 if (worker.getUcumService() == null) { 2693 return makeBoolean(false); 2694 } else { 2695 var lq = makeQuantity(left.get(0)); 2696 var rq = makeQuantity(right.get(0)); 2697 List<Base> dl = new ArrayList<Base>(); 2698 dl.add(qtyToCanonicalDecimal(lq)); 2699 List<Base> dr = new ArrayList<Base>(); 2700 dr.add(qtyToCanonicalDecimal(rq)); 2701 return opGreaterOrEqual(dl, dr, expr); 2702 } 2703 } 2704 } 2705 return new ArrayList<Base>(); 2706 } 2707 2708 private List<Base> opMemberOf(ExecutionContext context, List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2709 boolean ans = false; 2710 String url = right.get(0).primitiveValue(); 2711 ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url); 2712 if (vs != null) { 2713 for (Base l : left) { 2714 if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { 2715 if (worker.validateCode(terminologyServiceOptions.withGuessSystem() , TypeConvertor.castToCoding(l), vs).isOk()) { 2716 ans = true; 2717 } 2718 } else if (l.fhirType().equals("Coding")) { 2719 if (worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk()) { 2720 ans = true; 2721 } 2722 } else if (l.fhirType().equals("CodeableConcept")) { 2723 CodeableConcept cc = TypeConvertor.castToCodeableConcept(l); 2724 ValidationResult vr = worker.validateCode(terminologyServiceOptions, cc, vs); 2725 if (vr.isOk()) { 2726 ans = true; 2727 } 2728 } else { 2729 // DO NOTHING 2730 } 2731 } 2732 } 2733 return makeBoolean(ans); 2734 } 2735 2736 private List<Base> opIn(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException { 2737 if (left.size() == 0) { 2738 return new ArrayList<Base>(); 2739 } 2740 if (right.size() == 0) { 2741 return makeBoolean(false); 2742 } 2743 if (left.size() > 1) { 2744 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "in", convertToString(left)); 2745 } 2746 boolean ans = true; 2747 for (Base l : left) { 2748 boolean f = false; 2749 for (Base r : right) { 2750 Boolean eq = doEquals(l, r); 2751 if (eq != null && eq == true) { 2752 f = true; 2753 break; 2754 } 2755 } 2756 if (!f) { 2757 ans = false; 2758 break; 2759 } 2760 } 2761 return makeBoolean(ans); 2762 } 2763 2764 private List<Base> opContains(List<Base> left, List<Base> right, ExpressionNode expr) { 2765 if (right.size() == 0) { 2766 return new ArrayList<Base>(); 2767 } 2768 if (left.size() == 0) { 2769 return makeBoolean(false); 2770 } 2771 boolean ans = true; 2772 for (Base r : right) { 2773 boolean f = false; 2774 for (Base l : left) { 2775 Boolean eq = doEquals(l, r); 2776 if (eq != null && eq == true) { 2777 f = true; 2778 break; 2779 } 2780 } 2781 if (!f) { 2782 ans = false; 2783 break; 2784 } 2785 } 2786 return makeBoolean(ans); 2787 } 2788 2789 private List<Base> opPlus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2790 if (left.size() == 0 || right.size() == 0) { 2791 return new ArrayList<Base>(); 2792 } 2793 if (left.size() > 1) { 2794 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "+", convertToString(left)); 2795 } 2796 if (!left.get(0).isPrimitive()) { 2797 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "+", left.get(0).fhirType()); 2798 } 2799 if (right.size() > 1) { 2800 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "+"); 2801 } 2802 if (!right.get(0).isPrimitive() && !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant", "time") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) { 2803 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "+", right.get(0).fhirType()); 2804 } 2805 2806 List<Base> result = new ArrayList<Base>(); 2807 Base l = left.get(0); 2808 Base r = right.get(0); 2809 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 2810 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 2811 } else if (l.hasType("integer") && r.hasType("integer")) { 2812 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 2813 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2814 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 2815 } else if (l.hasType("date") && r.hasType("Quantity")) { 2816 DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 2817 var rq = makeQuantity(r); 2818 result.add(dateAdd(dl, rq, false, expr)); 2819 } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { 2820 DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 2821 var rq = makeQuantity(r); 2822 result.add(dateAdd(dl, rq, false, expr)); 2823 } else if (l.hasType("time") && r.hasType("Quantity")) { 2824 TimeType dl = l instanceof TimeType ? (TimeType) l : new TimeType(l.primitiveValue()); 2825 var rq = makeQuantity(r); 2826 var rdt = timeAdd(dl, rq, false, expr); 2827 result.add(rdt); // we only want the time part 2828 } else { 2829 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType()); 2830 } 2831 return result; 2832 } 2833 2834 private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) { 2835 BaseDateTimeType result = (BaseDateTimeType) d.copy(); 2836 2837 int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue(); 2838 switch (q.hasCode() ? q.getCode() : q.getUnit()) { 2839 case "years": 2840 case "year": 2841 result.add(Calendar.YEAR, value); 2842 break; 2843 case "a": 2844 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); 2845 case "months": 2846 case "month": 2847 result.add(Calendar.MONTH, value); 2848 break; 2849 case "mo": 2850 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString()); 2851 case "weeks": 2852 case "week": 2853 case "wk": 2854 result.add(Calendar.DAY_OF_MONTH, value * 7); 2855 break; 2856 case "days": 2857 case "day": 2858 case "d": 2859 result.add(Calendar.DAY_OF_MONTH, value); 2860 break; 2861 case "hours": 2862 case "hour": 2863 case "h": 2864 result.add(Calendar.HOUR, value); 2865 break; 2866 case "minutes": 2867 case "minute": 2868 case "min": 2869 result.add(Calendar.MINUTE, value); 2870 break; 2871 case "seconds": 2872 case "second": 2873 case "s": 2874 result.add(Calendar.SECOND, value); 2875 double decValue = negate ? 0 - q.getValue().doubleValue() : q.getValue().doubleValue(); 2876 decValue = decValue - value; // remove the integer part, so we can add it in milliseconds 2877 int ms = (int) (decValue * 1000); 2878 if (ms != 0) { 2879 result.add(Calendar.MILLISECOND, ms); 2880 } 2881 break; 2882 case "milliseconds": 2883 case "millisecond": 2884 case "ms": 2885 result.add(Calendar.MILLISECOND, value); 2886 break; 2887 default: 2888 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString()); 2889 } 2890 return result; 2891 } 2892 2893private TimeType timeAdd(TimeType d, Quantity q, boolean negate, ExpressionNode holder) { 2894 TimeType result = (TimeType) d.copy(); 2895 2896 int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue(); 2897 switch (q.hasCode() ? q.getCode() : q.getUnit()) { 2898 case "hours": 2899 case "hour": 2900 case "h": 2901 result.add(Calendar.HOUR, value); 2902 break; 2903 case "minutes": 2904 case "minute": 2905 case "min": 2906 result.add(Calendar.MINUTE, value); 2907 break; 2908 case "seconds": 2909 case "second": 2910 case "s": 2911 result.add(Calendar.SECOND, value); 2912 double decValue = negate ? 0 - q.getValue().doubleValue() : q.getValue().doubleValue(); 2913 decValue = decValue - value; // remove the integer part, so we can add it in milliseconds 2914 int ms = (int) (decValue * 1000); 2915 if (ms != 0) { 2916 result.add(Calendar.MILLISECOND, ms); 2917 } 2918 break; 2919 case "milliseconds": 2920 case "millisecond": 2921 case "ms": 2922 result.add(Calendar.MILLISECOND, value); 2923 break; 2924 default: 2925 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString()); 2926 } 2927 return result; 2928 } 2929 2930 private List<Base> opTimes(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2931 if (left.size() == 0 || right.size() == 0) { 2932 return new ArrayList<Base>(); 2933 } 2934 if (left.size() > 1) { 2935 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "*", convertToString(left)); 2936 } 2937 var lq = makeQuantity(left.get(0)); 2938 if (!left.get(0).isPrimitive() && !(lq instanceof Quantity)) { 2939 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType()); 2940 } 2941 var rq = makeQuantity(right.get(0)); 2942 if (right.size() > 1) { 2943 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "*"); 2944 } 2945 if (!right.get(0).isPrimitive() && !(rq instanceof Quantity)) { 2946 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "*", right.get(0).fhirType()); 2947 } 2948 2949 List<Base> result = new ArrayList<Base>(); 2950 Base l = left.get(0); 2951 Base r = right.get(0); 2952 2953 if (l.hasType("integer") && r.hasType("integer")) { 2954 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 2955 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2956 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 2957 } else if (lq instanceof Quantity && rq instanceof Quantity && worker.getUcumService() != null) { 2958 Pair pl = qtyToPair(lq); 2959 Pair pr = qtyToPair(rq); 2960 Pair p; 2961 try { 2962 p = worker.getUcumService().multiply(pl, pr); 2963 result.add(pairToQty(p)); 2964 } catch (UcumException e) { 2965 throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #TODO: i18n 2966 } 2967 } else { 2968 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType()); 2969 } 2970 return result; 2971 } 2972 2973 2974 private List<Base> opConcatenate(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 2975 if (left.size() > 1) { 2976 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&", convertToString(left)); 2977 } 2978 if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) { 2979 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType()); 2980 } 2981 if (right.size() > 1) { 2982 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "&"); 2983 } 2984 if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) { 2985 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType()); 2986 } 2987 2988 List<Base> result = new ArrayList<Base>(); 2989 String l = left.size() == 0 ? "" : left.get(0).primitiveValue(); 2990 String r = right.size() == 0 ? "" : right.get(0).primitiveValue(); 2991 result.add(new StringType(l + r)); 2992 return result; 2993 } 2994 2995 private List<Base> opUnion(List<Base> left, List<Base> right, ExpressionNode expr) { 2996 List<Base> result = new ArrayList<Base>(); 2997 for (Base item : left) { 2998 if (!doContains(result, item)) { 2999 result.add(item); 3000 } 3001 } 3002 for (Base item : right) { 3003 if (!doContains(result, item)) { 3004 result.add(item); 3005 } 3006 } 3007 return result; 3008 } 3009 3010 private boolean doContains(List<Base> list, Base item) { 3011 for (Base test : list) { 3012 Boolean eq = doEquals(test, item); 3013 if (eq != null && eq == true) { 3014 return true; 3015 } 3016 } 3017 return false; 3018 } 3019 3020 3021 private List<Base> opAnd(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3022 Equality l = asBool(left, expr); 3023 Equality r = asBool(right, expr); 3024 switch (l) { 3025 case False: return makeBoolean(false); 3026 case Null: 3027 if (r == Equality.False) { 3028 return makeBoolean(false); 3029 } else { 3030 return makeNull(); 3031 } 3032 case True: 3033 switch (r) { 3034 case False: return makeBoolean(false); 3035 case Null: return makeNull(); 3036 case True: return makeBoolean(true); 3037 } 3038 } 3039 return makeNull(); 3040 } 3041 3042 private boolean isBoolean(List<Base> list, boolean b) { 3043 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 3044 } 3045 3046 private List<Base> opOr(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3047 Equality l = asBool(left, expr); 3048 Equality r = asBool(right, expr); 3049 switch (l) { 3050 case True: return makeBoolean(true); 3051 case Null: 3052 if (r == Equality.True) { 3053 return makeBoolean(true); 3054 } else { 3055 return makeNull(); 3056 } 3057 case False: 3058 switch (r) { 3059 case False: return makeBoolean(false); 3060 case Null: return makeNull(); 3061 case True: return makeBoolean(true); 3062 } 3063 } 3064 return makeNull(); 3065 } 3066 3067 private List<Base> opXor(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3068 Equality l = asBool(left, expr); 3069 Equality r = asBool(right, expr); 3070 switch (l) { 3071 case True: 3072 switch (r) { 3073 case False: return makeBoolean(true); 3074 case True: return makeBoolean(false); 3075 case Null: return makeNull(); 3076 } 3077 case Null: 3078 return makeNull(); 3079 case False: 3080 switch (r) { 3081 case False: return makeBoolean(false); 3082 case True: return makeBoolean(true); 3083 case Null: return makeNull(); 3084 } 3085 } 3086 return makeNull(); 3087 } 3088 3089 private List<Base> opImplies(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3090 Equality eq = asBool(left, expr); 3091 if (eq == Equality.False) { 3092 return makeBoolean(true); 3093 } else if (right.size() == 0) { 3094 return makeNull(); 3095 } else switch (asBool(right, expr)) { 3096 case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); 3097 case Null: return makeNull(); 3098 case True: return makeBoolean(true); 3099 } 3100 return makeNull(); 3101 } 3102 3103 3104 private List<Base> opMinus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3105 if (left.size() == 0 || right.size() == 0) { 3106 return new ArrayList<Base>(); 3107 } 3108 if (left.size() > 1) { 3109 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "-", convertToString(left)); 3110 } 3111 if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) { 3112 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType()); 3113 } 3114 if (right.size() > 1) { 3115 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "-"); 3116 } 3117 if (!right.get(0).isPrimitive() && !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant", "time") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) { 3118 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType()); 3119 } 3120 3121 List<Base> result = new ArrayList<Base>(); 3122 Base l = left.get(0); 3123 Base r = right.get(0); 3124 3125 if (l.hasType("integer") && r.hasType("integer")) { 3126 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 3127 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3128 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 3129 } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { 3130 String s = l.primitiveValue(); 3131 if ("0".equals(s)) { 3132 Quantity qty = makeQuantity(r); 3133 result.add(qty.copy().setValue(qty.getValue().abs())); 3134 } 3135 } else if (l.hasType("date") && r.hasType("Quantity")) { 3136 DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 3137 result.add(dateAdd(dl, makeQuantity(r), true, expr)); 3138 } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) { 3139 DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 3140 result.add(dateAdd(dl, makeQuantity(r), true, expr)); 3141 } else if (l.hasType("time") && r.hasType("Quantity")) { 3142 TimeType dl = l instanceof TimeType ? (TimeType) l : new TimeType(l.primitiveValue()); 3143 var rdt = timeAdd(dl, makeQuantity(r), true, expr); 3144 result.add(rdt); // we only want the time part 3145 } else { 3146 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType()); 3147 } 3148 return result; 3149 } 3150 3151 private List<Base> opDivideBy(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3152 if (left.size() == 0 || right.size() == 0) { 3153 return new ArrayList<Base>(); 3154 } 3155 if (left.size() > 1) { 3156 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "/", convertToString(left)); 3157 } 3158 var lq = makeQuantity(left.get(0)); 3159 if (!left.get(0).isPrimitive() && !(lq instanceof Quantity)) { 3160 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType()); 3161 } 3162 if (right.size() > 1) { 3163 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "/"); 3164 } 3165 var rq = makeQuantity(right.get(0)); 3166 if (!right.get(0).isPrimitive() && !(rq instanceof Quantity)) { 3167 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType()); 3168 } 3169 3170 List<Base> result = new ArrayList<Base>(); 3171 Base l = left.get(0); 3172 Base r = right.get(0); 3173 3174 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 3175 Decimal d1; 3176 try { 3177 d1 = new Decimal(l.primitiveValue()); 3178 Decimal d2 = new Decimal(r.primitiveValue()); 3179 result.add(new DecimalType(d1.divide(d2).asDecimal())); 3180 } catch (UcumException e) { 3181 // just return nothing 3182 } 3183 } else if (lq instanceof Quantity && rq instanceof Quantity && worker.getUcumService() != null) { 3184 Pair pl = qtyToPair(lq); 3185 Pair pr = qtyToPair(rq); 3186 Pair p; 3187 try { 3188 p = worker.getUcumService().divideBy(pl, pr); 3189 result.add(pairToQty(p)); 3190 } catch (UcumException e) { 3191 // just return nothing 3192 } 3193 } else { 3194 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType()); 3195 } 3196 return result; 3197 } 3198 3199 private List<Base> opDiv(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3200 if (left.size() == 0 || right.size() == 0) { 3201 return new ArrayList<Base>(); 3202 } 3203 if (left.size() > 1) { 3204 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "div", convertToString(left)); 3205 } 3206 if (!left.get(0).isPrimitive()) { 3207 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType()); 3208 } 3209 if (right.size() > 1) { 3210 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "div"); 3211 } 3212 if (!right.get(0).isPrimitive()) { 3213 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType()); 3214 } 3215 3216 List<Base> result = new ArrayList<Base>(); 3217 Base l = left.get(0); 3218 Base r = right.get(0); 3219 3220 if (l.hasType("integer") && r.hasType("integer")) { 3221 int divisor = Integer.parseInt(r.primitiveValue()); 3222 if (divisor != 0) { 3223 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor)); 3224 } 3225 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3226 Decimal d1; 3227 try { 3228 d1 = new Decimal(l.primitiveValue()); 3229 Decimal d2 = new Decimal(r.primitiveValue()); 3230 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 3231 } catch (UcumException e) { 3232 // just return nothing 3233 } 3234 } else { 3235 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType()); 3236 } 3237 return result; 3238 } 3239 3240 private List<Base> opMod(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException { 3241 if (left.size() == 0 || right.size() == 0) { 3242 return new ArrayList<Base>(); 3243 } if (left.size() > 1) { 3244 throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod", convertToString(left)); 3245 } 3246 if (!left.get(0).isPrimitive()) { 3247 throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType()); 3248 } 3249 if (right.size() > 1) { 3250 throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "mod"); 3251 } 3252 if (!right.get(0).isPrimitive()) { 3253 throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType()); 3254 } 3255 3256 List<Base> result = new ArrayList<Base>(); 3257 Base l = left.get(0); 3258 Base r = right.get(0); 3259 3260 if (l.hasType("integer") && r.hasType("integer")) { 3261 int modulus = Integer.parseInt(r.primitiveValue()); 3262 if (modulus != 0) { 3263 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus)); 3264 } 3265 } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 3266 Decimal d1; 3267 try { 3268 d1 = new Decimal(l.primitiveValue()); 3269 Decimal d2 = new Decimal(r.primitiveValue()); 3270 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 3271 } catch (UcumException e) { 3272 throw new PathEngineException(e); 3273 } 3274 } else { 3275 throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType()); 3276 } 3277 return result; 3278 } 3279 3280 3281 private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, FHIRPathConstantEvaluationMode mode) throws PathEngineException { 3282 if (constant instanceof BooleanType) { 3283 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3284 } else if (constant instanceof IntegerType) { 3285 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3286 } else if (constant instanceof DecimalType) { 3287 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3288 } else if (constant instanceof FHIRConstant) { 3289 return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr, mode); 3290 } else if (constant == null) { 3291 return new TypeDetails(CollectionStatus.SINGLETON); 3292 } else 3293 { 3294 var q = makeQuantity(constant); 3295 if (q instanceof Quantity) { 3296 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 3297 } else { 3298 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3299 } 3300 } 3301 } 3302 3303 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, FHIRPathConstantEvaluationMode mode) throws PathEngineException { 3304 if (s.startsWith("@")) { 3305 if (s.startsWith("@T")) { 3306 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 3307 } else { 3308 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3309 } 3310 } else if (s.equals("%sct")) { 3311 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3312 } else if (s.equals("%loinc")) { 3313 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3314 } else if (s.equals("%ucum")) { 3315 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3316 } else if (s.equals("%resource")) { 3317 if (context.resource == null) { 3318 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource"); 3319 } 3320 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 3321 } else if (s.equals("%rootResource")) { 3322 if (context.rootResource == null) { 3323 throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource"); 3324 } 3325 return new TypeDetails(CollectionStatus.SINGLETON, context.rootResource); 3326 } else if (s.equals("%context")) { 3327 return context.context; 3328 } else if (s.equals("%map-codes")) { 3329 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3330 } else if (s.equals("%us-zip")) { 3331 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3332 } else if (s.startsWith("%`vs-")) { 3333 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3334 } else if (s.startsWith("%`cs-")) { 3335 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3336 } else if (s.startsWith("%`ext-")) { 3337 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3338 } else if (hostServices == null) { 3339 String varName = s.substring(1); 3340 if (context.hasDefinedVariable(varName)) 3341 return context.getDefinedVariable(varName); 3342 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 3343 } else { 3344 String varName = s.substring(1); 3345 if (context.hasDefinedVariable(varName)) 3346 return context.getDefinedVariable(varName); 3347 TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, mode == FHIRPathConstantEvaluationMode.EXPLICIT ? s.substring(1) : s, mode); 3348 if (v == null) { 3349 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 3350 } else { 3351 return v; 3352 } 3353 } 3354 } 3355 3356 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 3357 List<Base> result = new ArrayList<Base>(); 3358 if (atEntry && context.appInfo != null && hostServices != null && checkWithHostServicesBeforeHand) { 3359 // we'll see if the name matches a constant known by the context. 3360 List<Base> temp = hostServices.resolveConstant(this, context.appInfo, exp.getName(), FHIRPathConstantEvaluationMode.IMPLICIT_BEFORE); 3361 if (!temp.isEmpty()) { 3362 result.addAll(temp); 3363 return result; 3364 } 3365 } 3366 if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 3367 StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType()); 3368 if (sd == null) { 3369 // logical model 3370 if (exp.getName().equals(item.fhirType())) { 3371 result.add(item); 3372 } 3373 } else { 3374 while (sd != null) { 3375 if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) { 3376 result.add(item); 3377 break; 3378 } 3379 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 3380 } 3381 } 3382 } else { 3383 getChildrenByName(item, exp.getName(), result); 3384 } 3385 if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) { 3386 // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. 3387 // (if the name does match, and the user wants to get the constant value, they'll have to try harder... 3388 result.addAll(hostServices.resolveConstant(this, context.appInfo, exp.getName(), FHIRPathConstantEvaluationMode.IMPLICIT_AFTER)); 3389 } 3390 return result; 3391 } 3392 3393 private String getParent(String rn) { 3394 return null; 3395 } 3396 3397 3398 private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, FHIRPathConstantEvaluationMode mode) throws PathEngineException, DefinitionException { 3399 if (hostServices == null) { 3400 throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference"); 3401 } 3402 return hostServices.resolveConstantType(this, context.appInfo, name, mode); 3403 } 3404 3405 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException { 3406 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up 3407 return new TypeDetails(CollectionStatus.SINGLETON, type); 3408 } 3409 TypeDetails result = new TypeDetails(focus.getCollectionStatus()); 3410 getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies); 3411 return result; 3412 } 3413 3414 3415 private boolean isAncestor(String wanted, String stated) { 3416 try { 3417 StructureDefinition sd = worker.fetchTypeDefinition(wanted); 3418 while (sd != null) { 3419 if (stated.equals(sd.getTypeName())) { 3420 return true; 3421 } 3422 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 3423 } 3424 return false; 3425 } catch (Exception e) { 3426 return false; 3427 } 3428 } 3429 3430 private String hashTail(String type) { 3431 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); 3432 } 3433 3434 private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, List<TypeDetails> paramTypes, boolean canBeNone) { 3435 for (ExpressionNode exprParam : exp.getParameters()) { 3436 if (hasExpressionParameter(exp)) { 3437 var newContext = changeThis(context, focus); 3438 paramTypes.add(executeType(newContext, newContext.thisItem, exprParam, elementDependencies, true, canBeNone, exp)); 3439 } else { 3440 paramTypes.add(executeType(context, context.thisItem, exprParam, elementDependencies, true, canBeNone, exp)); 3441 } 3442 } 3443 } 3444 3445 @SuppressWarnings("unchecked") 3446 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, ExpressionNode container) throws PathEngineException, DefinitionException { 3447 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 3448 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) { 3449 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3450 } else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) { 3451 TypeDetails base = TypeDetails.empty(); 3452 TypeDetails lFocus = focus; 3453 boolean changed = false; 3454 do { 3455 evaluateParameters(context, lFocus, exp, elementDependencies, paramTypes, true); 3456 changed = false; 3457 if (!base.contains(paramTypes.get(0))) { 3458 changed = true; 3459 base.addTypes(paramTypes.get(0)); 3460 lFocus = base; 3461 } 3462 } while (changed); 3463 paramTypes.clear(); 3464 paramTypes.add(base); 3465 } else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists || 3466 exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue 3467 || exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) { 3468 evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false); 3469 } else { 3470 evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false); 3471 } 3472 if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) { 3473 if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) { 3474 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION)); 3475 3476 } 3477 } 3478 switch (exp.getFunction()) { 3479 case Empty : 3480 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3481 case Not : 3482 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3483 case Exists : { 3484 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3485 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3486 } 3487 case SubsetOf : { 3488 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered()); 3489 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3490 } 3491 case SupersetOf : { 3492 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 3493 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3494 } 3495 case IsDistinct : 3496 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3497 case Distinct : 3498 return focus; 3499 case Count : 3500 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3501 case Where : 3502 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3503 // special case: where the focus is Reference, and the parameter to where is resolve() "is", we will suck up the target types 3504 if (focus.hasType("Reference")) { 3505 boolean canRestrictTargets = !exp.getParameters().isEmpty(); 3506 List<String> targets = new ArrayList<>(); 3507 if (canRestrictTargets) { 3508 ExpressionNode p = exp.getParameters().get(0); 3509 if (p.getKind() == Kind.Function && p.getName().equals("resolve") && p.getOperation() == Operation.Is) { 3510 targets.add(p.getOpNext().getName()); 3511 } else { 3512 canRestrictTargets = false; 3513 } 3514 } 3515 if (canRestrictTargets) { 3516 TypeDetails td = focus.copy(); 3517 td.getTargets().clear(); 3518 td.getTargets().addAll(targets); 3519 return td; 3520 } else { 3521 return focus; 3522 } 3523 } else { 3524 return focus; 3525 } 3526 case Select : 3527 return paramTypes.get(0); 3528 case All : 3529 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3530 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3531 case Repeat : 3532 return paramTypes.get(0); 3533 case Aggregate : 3534 return anything(focus.getCollectionStatus()); 3535 case Item : { 3536 checkOrdered(focus, "item", exp); 3537 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3538 return focus.toSingleton(); 3539 } 3540 case As : { 3541 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3542 String tn = checkType(focus, exp); 3543 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); 3544 if (td.typesHaveTargets()) { 3545 td.addTargets(focus.getTargets()); 3546 } 3547 return td; 3548 } 3549 case OfType : { 3550 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3551 String tn = checkType(focus, exp); 3552 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); 3553 if (td.typesHaveTargets()) { 3554 td.addTargets(focus.getTargets()); 3555 } 3556 return td; 3557 } 3558 case Type : { 3559 boolean s = false; 3560 boolean c = false; 3561 for (ProfiledType pt : focus.getProfiledTypes()) { 3562 s = s || pt.isSystemType(); 3563 c = c || !pt.isSystemType(); 3564 } 3565 if (s && c) { 3566 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); 3567 } else if (s) { 3568 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); 3569 } else { 3570 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); 3571 } 3572 } 3573 case Is : { 3574 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3575 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3576 } 3577 case Single : 3578 return focus.toSingleton(); 3579 case First : { 3580 checkOrdered(focus, "first", exp); 3581 return focus.toSingleton(); 3582 } 3583 case Last : { 3584 checkOrdered(focus, "last", exp); 3585 return focus.toSingleton(); 3586 } 3587 case Tail : { 3588 checkOrdered(focus, "tail", exp); 3589 return focus; 3590 } 3591 case Skip : { 3592 checkOrdered(focus, "skip", exp); 3593 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3594 return focus; 3595 } 3596 case Take : { 3597 checkOrdered(focus, "take", exp); 3598 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3599 return focus; 3600 } 3601 case Union : { 3602 return focus.union(paramTypes.get(0)); 3603 } 3604 case Combine : { 3605 return focus.union(paramTypes.get(0)); 3606 } 3607 case Intersect : { 3608 return focus.intersect(paramTypes.get(0)); 3609 } 3610 case Exclude : { 3611 return focus; 3612 } 3613 case Iif : { 3614 TypeDetails types = new TypeDetails(null); 3615 checkSingleton(focus, "iif", exp); 3616 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 3617 types.update(paramTypes.get(1)); 3618 if (paramTypes.size() > 2) { 3619 types.update(paramTypes.get(2)); 3620 } 3621 return types; 3622 } 3623 case Lower : { 3624 checkContextString(focus, "lower", exp, true); 3625 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3626 } 3627 case Upper : { 3628 checkContextString(focus, "upper", exp, true); 3629 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3630 } 3631 case ToChars : { 3632 checkContextString(focus, "toChars", exp, true); 3633 return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 3634 } 3635 case IndexOf : { 3636 checkContextString(focus, "indexOf", exp, true); 3637 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3638 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3639 } 3640 case Substring : { 3641 checkContextString(focus, "subString", exp, true); 3642 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3643 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3644 } 3645 case StartsWith : { 3646 checkContextString(focus, "startsWith", exp, true); 3647 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3648 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3649 } 3650 case EndsWith : { 3651 checkContextString(focus, "endsWith", exp, true); 3652 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3653 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3654 } 3655 case Matches : { 3656 checkContextString(focus, "matches", exp, true); 3657 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3658 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3659 } 3660 case MatchesFull : { 3661 checkContextString(focus, "matches", exp, true); 3662 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3663 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3664 } 3665 case ReplaceMatches : { 3666 checkContextString(focus, "replaceMatches", exp, true); 3667 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3668 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3669 } 3670 case Contains : { 3671 checkContextString(focus, "contains", exp, true); 3672 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3673 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3674 } 3675 case Replace : { 3676 checkContextString(focus, "replace", exp, true); 3677 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3678 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3679 } 3680 case Length : { 3681 checkContextPrimitive(focus, "length", false, exp); 3682 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3683 } 3684 case Children : 3685 return childTypes(focus, "*", exp); 3686 case Descendants : 3687 return childTypes(focus, "**", exp); 3688 case MemberOf : { 3689 checkContextCoded(focus, "memberOf", exp); 3690 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3691 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3692 } 3693 case Trace : { 3694 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3695 return focus; 3696 } 3697 case DefineVariable : { 3698 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String)); 3699 // set the type of the variable 3700 // Actually evaluate the value of the first parameter (to get the name of the variable if possible) 3701 // and if have that, set it into the context 3702 ExpressionNode p = exp.getParameters().get(0); 3703 if (p.getKind() == Kind.Constant && p.getConstant() != null) { 3704 String varName = exp.getParameters().get(0).getConstant().primitiveValue(); 3705 if (varName != null) { 3706 if (paramTypes.size() > 1) 3707 context.setDefinedVariable(varName, paramTypes.get(1)); 3708 else 3709 context.setDefinedVariable(varName, focus); 3710 } 3711 } else { 3712 // this variable is not a constant, so we can't analyze what name it could have 3713 } 3714 return focus; 3715 } 3716 case Check : { 3717 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3718 return focus; 3719 } 3720 case Today : 3721 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3722 case Now : 3723 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3724 case Resolve : { 3725 checkContextReference(focus, "resolve", exp); 3726 return new TypeDetails(focus.getCollectionStatus(), "Resource"); 3727 } 3728 case Extension : { 3729 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3730 ExpressionNode p = exp.getParameters().get(0); 3731 if (p.getKind() == Kind.Constant && p.getConstant() != null) { 3732 String url = exp.getParameters().get(0).getConstant().primitiveValue(); 3733 if (!Utilities.isAbsoluteUrl(url) && focus.hasType("Extension")) { 3734 TypeDetails res = new TypeDetails(CollectionStatus.ORDERED); 3735 List<String> profiles = focus.getProfiles("Extension"); 3736 if (profiles != null) { 3737 for (String pt : profiles) { 3738 String extn = pt.contains("#") ? pt.substring(0, pt.indexOf("#")) : pt; 3739 String subExtn = pt.contains("#") ? pt.substring(0, pt.indexOf("#")) : null; 3740 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, extn); 3741 if (sd != null) { 3742 String id = subExtn == null ? "Extension.extension:"+url : subExtn+".extension:"+url; 3743 ElementDefinition ed = sd.getSnapshot().getElementById(id); 3744 if (ed != null) { 3745 res.addType("Extension", sd.getUrl()+"#"+id); 3746 } 3747 } 3748 } 3749 } 3750 if (res.isEmpty()) { 3751 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION)); 3752 } else { 3753 return res; 3754 } 3755 } else { 3756 ExtensionDefinition ed = findExtensionDefinition(focus, url); 3757 if (ed != null) { 3758 return new TypeDetails(CollectionStatus.ORDERED, new ProfiledType(ed.sd.getUrl())); 3759 } else { 3760 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION)); 3761 } 3762 } 3763 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 3764 } 3765 } 3766 case AnyTrue: 3767 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3768 case AllTrue: 3769 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3770 case AnyFalse: 3771 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3772 case AllFalse: 3773 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3774 case HasValue : 3775 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3776 case HtmlChecks1 : 3777 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3778 case HtmlChecks2 : 3779 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3780 case Comparable : 3781 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3782 case Encode: 3783 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3784 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3785 case Decode: 3786 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3787 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3788 case Escape: 3789 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3790 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3791 case Unescape: 3792 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3793 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3794 case Trim: 3795 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3796 case Split: 3797 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3798 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3799 case Join: 3800 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3801 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3802 case ToInteger : { 3803 checkContextPrimitive(focus, "toInteger", true, exp); 3804 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3805 } 3806 case ToDecimal : { 3807 checkContextPrimitive(focus, "toDecimal", true, exp); 3808 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3809 } 3810 case ToString : { 3811 checkContextPrimitive(focus, "toString", true, exp); 3812 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 3813 } 3814 case ToQuantity : { 3815 checkContextPrimitive(focus, "toQuantity", true, exp); 3816 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 3817 } 3818 case ToBoolean : { 3819 checkContextPrimitive(focus, "toBoolean", false, exp); 3820 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3821 } 3822 case ToDateTime : { 3823 checkContextPrimitive(focus, "ToDateTime", false, exp); 3824 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3825 } 3826 case ToTime : { 3827 checkContextPrimitive(focus, "ToTime", false, exp); 3828 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 3829 } 3830 case ConvertsToString : 3831 case ConvertsToQuantity :{ 3832 checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp); 3833 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3834 } 3835 case ConvertsToInteger : 3836 case ConvertsToDecimal : 3837 case ConvertsToDateTime : 3838 case ConvertsToDate : 3839 case ConvertsToTime : 3840 case ConvertsToBoolean : { 3841 checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp); 3842 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3843 } 3844 case ConformsTo: { 3845 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3846 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3847 } 3848 case Abs : { 3849 checkContextNumerical(focus, "abs", exp); 3850 return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); 3851 } 3852 case Sort : 3853 return new TypeDetails(CollectionStatus.ORDERED, focus.getTypes()); 3854 case Coalesce : { 3855 TypeDetails types = new TypeDetails(null); 3856 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes); 3857 for (var t : paramTypes) { 3858 types.update(t); 3859 } 3860 return types; 3861 } 3862 case Truncate : 3863 case Floor : 3864 case Ceiling : { 3865 checkContextDecimal(focus, exp.getFunction().toCode(), exp); 3866 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3867 } 3868 3869 case Round :{ 3870 checkContextDecimal(focus, "round", exp); 3871 if (paramTypes.size() > 0) { 3872 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3873 } 3874 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3875 } 3876 3877 case Exp : 3878 case Ln : 3879 case Sqrt : { 3880 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3881 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3882 } 3883 case Log : { 3884 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3885 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); 3886 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3887 } 3888 case Power : { 3889 checkContextNumerical(focus, exp.getFunction().toCode(), exp); 3890 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS)); 3891 return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes()); 3892 } 3893 3894 case LowBoundary: 3895 case HighBoundary: { 3896 checkContextContinuous(focus, exp.getFunction().toCode(), exp, true); 3897 if (paramTypes.size() > 0) { 3898 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 3899 } 3900 if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { 3901 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); 3902 } else if ((focus.hasType("time"))) { 3903 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time, TypeDetails.FP_Time); 3904 } else if (focus.hasType("decimal") || focus.hasType("integer")) { 3905 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3906 } else { 3907 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3908 } 3909 } 3910 case Precision: { 3911 checkContextContinuous(focus, exp.getFunction().toCode(), exp, false); 3912 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3913 } 3914 case hasTemplateIdOf: { 3915 checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 3916 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 3917 } 3918 case Debug: { 3919 return focus; 3920 } 3921 case Custom : { 3922 return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes); 3923 } 3924 default: 3925 break; 3926 } 3927 throw new Error("not Implemented yet"); 3928 } 3929 3930 private ExtensionDefinition findExtensionDefinition(TypeDetails focus, String url) { 3931 if (Utilities.isAbsoluteUrl(url)) { 3932 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 3933 if (sd == null) { 3934 return null; 3935 } else { 3936 return new ExtensionDefinition(true, sd, sd.getSnapshot().getElementFirstRep()); 3937 } 3938 } 3939 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, focus.getType()); 3940 if (sd != null) { 3941 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 3942 if (ed.hasFixed() && url.equals(ed.getFixed().primitiveValue())) { 3943 return new ExtensionDefinition(false, sd, ed); 3944 } 3945 } 3946 } 3947 return null; 3948 } 3949 3950 private String checkType(TypeDetails focus, ExpressionNode exp) { 3951 String tn; 3952 if (exp.getParameters().get(0).getInner() != null) { 3953 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 3954 } else { 3955 tn = "FHIR."+exp.getParameters().get(0).getName(); 3956 } 3957 if (tn.startsWith("System.")) { 3958 tn = tn.substring(7); 3959 } else if (tn.startsWith("FHIR.")) { 3960 tn = Utilities.pathURL(Constants.NS_FHIR_ROOT, "StructureDefinition", tn.substring(5)); 3961 } else if (tn.startsWith("CDA.")) { 3962 tn = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); 3963 } 3964 3965 if (typeCastIsImpossible(focus, tn)) { 3966 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE)); 3967 } 3968 return tn; 3969 } 3970 3971 private boolean typeCastIsImpossible(TypeDetails focus, String tn) { 3972 for (ProfiledType pt : focus.getProfiledTypes()) { 3973 if (!pt.hasProfiles()) { // get back to this later 3974 String purl = tn; 3975 while (purl != null && !purl.equals(pt.getUri())) { 3976 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, purl); 3977 purl = sd == null ? null : sd.getBaseDefinitionNoVersion(); 3978 } 3979 if (purl != null) { 3980 return false; 3981 } 3982 } 3983 } 3984 return !focus.hasType(tn); 3985 } 3986 3987 private boolean hasExpressionParameter(ExpressionNode exp) { 3988 if (exp.getFunction() == Function.Sort) { 3989 return true; 3990 } 3991 if (exp.getFunction() == Function.Iif) { 3992 return true; 3993 } 3994 3995 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) 3996 return true; 3997 Set<Function> functionsWithExpressionParameter = Set.of(Function.Trace, Function.DefineVariable); 3998 if (functionsWithExpressionParameter.contains(exp.getFunction())) 3999 return true; 4000 return false; 4001 } 4002 4003 4004 private void checkParamTypes(ExpressionNode expr, String funcName,List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 4005 int i = 0; 4006 for (TypeDetails pt : typeSet) { 4007 if (i == paramTypes.size()) { 4008 return; 4009 } 4010 TypeDetails actual = paramTypes.get(i); 4011 i++; 4012 for (String a : actual.getTypes()) { 4013 if (!pt.hasType(worker, a)) { 4014 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString()); 4015 } 4016 } 4017 if (actual.getCollectionStatus() != CollectionStatus.SINGLETON && pt.getCollectionStatus() == CollectionStatus.SINGLETON) { 4018 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER, funcName, i, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER)); 4019 } 4020 } 4021 } 4022 4023 private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4024 if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) { 4025 typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT)); 4026 } 4027 } 4028 4029 private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4030 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) { 4031 throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name); 4032 } 4033 } 4034 4035 private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4036 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) { 4037 throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe()); 4038 } 4039 } 4040 4041 4042 private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4043 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) { 4044 throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe()); 4045 } 4046 } 4047 4048 4049 private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException { 4050 if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) { 4051 throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe()); 4052 } 4053 } 4054 4055 4056 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException { 4057 if (!focus.hasNoTypes()) { 4058 if (canQty) { 4059 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) { 4060 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString()); 4061 } 4062 } else if (!focus.hasType(primitiveTypes)) { 4063 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString()); 4064 } 4065 } 4066 } 4067 4068 private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4069 if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) { 4070 throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe()); 4071 } 4072 } 4073 4074 private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 4075 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) { 4076 throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe()); 4077 } 4078 } 4079 4080 private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException { 4081 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) { 4082 throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); 4083 } 4084 } 4085 4086 private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException { 4087 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 4088 for (String f : focus.getTypes()) { 4089 getChildTypesByName(f, mask, result, expr, null, null); 4090 } 4091 return result; 4092 } 4093 4094 private TypeDetails anything(CollectionStatus status) { 4095 return new TypeDetails(status, allTypes.keySet()); 4096 } 4097 4098 // private boolean isPrimitiveType(String s) { 4099 // 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"); 4100 // } 4101 4102 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4103 switch (exp.getFunction()) { 4104 case Empty : return funcEmpty(context, focus, exp); 4105 case Not : return funcNot(context, focus, exp); 4106 case Exists : return funcExists(context, focus, exp); 4107 case SubsetOf : return funcSubsetOf(context, focus, exp); 4108 case SupersetOf : return funcSupersetOf(context, focus, exp); 4109 case IsDistinct : return funcIsDistinct(context, focus, exp); 4110 case Distinct : return funcDistinct(context, focus, exp); 4111 case Count : return funcCount(context, focus, exp); 4112 case Where : return funcWhere(context, focus, exp); 4113 case Select : return funcSelect(context, focus, exp); 4114 case All : return funcAll(context, focus, exp); 4115 case Repeat : return funcRepeat(context, focus, exp); 4116 case Aggregate : return funcAggregate(context, focus, exp); 4117 case Item : return funcItem(context, focus, exp); 4118 case As : return funcAs(context, focus, exp); 4119 case OfType : return funcOfType(context, focus, exp); 4120 case Type : return funcType(context, focus, exp); 4121 case Is : return funcIs(context, focus, exp); 4122 case Single : return funcSingle(context, focus, exp); 4123 case First : return funcFirst(context, focus, exp); 4124 case Last : return funcLast(context, focus, exp); 4125 case Tail : return funcTail(context, focus, exp); 4126 case Skip : return funcSkip(context, focus, exp); 4127 case Take : return funcTake(context, focus, exp); 4128 case Union : return funcUnion(context, focus, exp); 4129 case Combine : return funcCombine(context, focus, exp); 4130 case Intersect : return funcIntersect(context, focus, exp); 4131 case Exclude : return funcExclude(context, focus, exp); 4132 case Iif : return funcIif(context, focus, exp); 4133 case Lower : return funcLower(context, focus, exp); 4134 case Upper : return funcUpper(context, focus, exp); 4135 case ToChars : return funcToChars(context, focus, exp); 4136 case IndexOf : return funcIndexOf(context, focus, exp); 4137 case Substring : return funcSubstring(context, focus, exp); 4138 case StartsWith : return funcStartsWith(context, focus, exp); 4139 case EndsWith : return funcEndsWith(context, focus, exp); 4140 case Matches : return funcMatches(context, focus, exp); 4141 case MatchesFull : return funcMatchesFull(context, focus, exp); 4142 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 4143 case Contains : return funcContains(context, focus, exp); 4144 case Replace : return funcReplace(context, focus, exp); 4145 case Length : return funcLength(context, focus, exp); 4146 case Children : return funcChildren(context, focus, exp); 4147 case Descendants : return funcDescendants(context, focus, exp); 4148 case MemberOf : return funcMemberOf(context, focus, exp); 4149 case Trace : return funcTrace(context, focus, exp); 4150 case DefineVariable : return funcDefineVariable(context, focus, exp); 4151 case Check : return funcCheck(context, focus, exp); 4152 case Today : return funcToday(context, focus, exp); 4153 case Now : return funcNow(context, focus, exp); 4154 case Resolve : return funcResolve(context, focus, exp); 4155 case Extension : return funcExtension(context, focus, exp); 4156 case AnyFalse: return funcAnyFalse(context, focus, exp); 4157 case AllFalse: return funcAllFalse(context, focus, exp); 4158 case AnyTrue: return funcAnyTrue(context, focus, exp); 4159 case AllTrue: return funcAllTrue(context, focus, exp); 4160 case HasValue : return funcHasValue(context, focus, exp); 4161 case Encode : return funcEncode(context, focus, exp); 4162 case Decode : return funcDecode(context, focus, exp); 4163 case Escape : return funcEscape(context, focus, exp); 4164 case Unescape : return funcUnescape(context, focus, exp); 4165 case Trim : return funcTrim(context, focus, exp); 4166 case Split : return funcSplit(context, focus, exp); 4167 case Join : return funcJoin(context, focus, exp); 4168 case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp); 4169 case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp); 4170 case Comparable : return funcComparable(context, focus, exp); 4171 case ToInteger : return funcToInteger(context, focus, exp); 4172 case ToDecimal : return funcToDecimal(context, focus, exp); 4173 case ToString : return funcToString(context, focus, exp); 4174 case ToBoolean : return funcToBoolean(context, focus, exp); 4175 case ToQuantity : return funcToQuantity(context, focus, exp); 4176 case ToDateTime : return funcToDateTime(context, focus, exp); 4177 case ToTime : return funcToTime(context, focus, exp); 4178 case ConvertsToInteger : return funcIsInteger(context, focus, exp); 4179 case ConvertsToDecimal : return funcIsDecimal(context, focus, exp); 4180 case ConvertsToString : return funcIsString(context, focus, exp); 4181 case ConvertsToBoolean : return funcIsBoolean(context, focus, exp); 4182 case ConvertsToQuantity : return funcIsQuantity(context, focus, exp); 4183 case ConvertsToDateTime : return funcIsDateTime(context, focus, exp); 4184 case ConvertsToDate : return funcIsDate(context, focus, exp); 4185 case ConvertsToTime : return funcIsTime(context, focus, exp); 4186 case ConformsTo : return funcConformsTo(context, focus, exp); 4187 case Round : return funcRound(context, focus, exp); 4188 case Sqrt : return funcSqrt(context, focus, exp); 4189 case Abs : return funcAbs(context, focus, exp); 4190 case Ceiling : return funcCeiling(context, focus, exp); 4191 case Exp : return funcExp(context, focus, exp); 4192 case Floor : return funcFloor(context, focus, exp); 4193 case Ln : return funcLn(context, focus, exp); 4194 case Log : return funcLog(context, focus, exp); 4195 case Power : return funcPower(context, focus, exp); 4196 case Truncate : return funcTruncate(context, focus, exp); 4197 case Sort : return funcSort(context, focus, exp); 4198 case Coalesce: return funcCoalesce(context, focus, exp); 4199 case LowBoundary : return funcLowBoundary(context, focus, exp); 4200 case HighBoundary : return funcHighBoundary(context, focus, exp); 4201 case Precision : return funcPrecision(context, focus, exp); 4202 case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp); 4203 case Debug: return funcDebug(context, focus, exp); 4204 4205 4206 case Custom: { 4207 List<List<Base>> params = new ArrayList<List<Base>>(); 4208 if (hostServices.paramIsType( exp.getName(), 0)) { 4209 if (exp.getParameters().size() > 0) { 4210 String tn; 4211 if (exp.getParameters().get(0).getInner() != null) { 4212 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 4213 } else { 4214 tn = "FHIR."+exp.getParameters().get(0).getName(); 4215 } 4216 List<Base> p = new ArrayList<>(); 4217 p.add(new CodeType(tn)); 4218 params.add(p); 4219 } 4220 } else { 4221 for (ExpressionNode p : exp.getParameters()) { 4222 params.add(execute(context, baseToList(context.thisItem), p, true)); 4223 } 4224 } 4225 return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params); 4226 } 4227 default: 4228 throw new Error("not Implemented yet"); 4229 } 4230 } 4231 4232 private List<Base> funcHasTemplateIdOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4233 List<Base> result = new ArrayList<Base>(); 4234 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4235 String sw = convertToString(swb); 4236 4237 StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw); 4238 if (focus.size() == 1 && sd != null) { 4239 boolean found = false; 4240 for (Identifier id : sd.getIdentifier()) { 4241 if (id.getValue().startsWith("urn:hl7ii:")) { 4242 String[] p = id.getValue().split("\\:"); 4243 if (p.length == 4) { 4244 found = found || hasTemplateId(focus.get(0), p[2], p[3]); 4245 } 4246 } else if (id.getValue().startsWith("urn:oid:")) { 4247 found = found || hasTemplateId(focus.get(0), id.getValue().substring(8)); 4248 } 4249 } 4250 result.add(new BooleanType(found)); 4251 } 4252 return result; 4253 } 4254 4255 private boolean hasTemplateId(Base base, String rv) { 4256 List<Base> templateIds = base.listChildrenByName("templateId"); 4257 for (Base templateId : templateIds) { 4258 Base root = templateId.getChildValueByName("root"); 4259 Base extension = templateId.getChildValueByName("extension"); 4260 if (extension == null && root != null && rv.equals(root.primitiveValue())) { 4261 return true; 4262 } 4263 } 4264 return false; 4265 } 4266 4267 private boolean hasTemplateId(Base base, String rv, String ev) { 4268 List<Base> templateIds = base.listChildrenByName("templateId"); 4269 for (Base templateId : templateIds) { 4270 Base root = templateId.getChildValueByName("root"); 4271 Base extension = templateId.getChildValueByName("extension"); 4272 if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) { 4273 return true; 4274 } 4275 } 4276 return false; 4277 } 4278 4279 private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4280 if (focus.size() == 0) { 4281 return new ArrayList<Base>(); 4282 } 4283 if (focus.size() != 1) { 4284 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); 4285 } 4286 Base base = focus.get(0); 4287 List<Base> result = new ArrayList<Base>(); 4288 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4289 Double d = Double.parseDouble(base.primitiveValue()); 4290 try { 4291 result.add(new DecimalType(Math.sqrt(d))); 4292 } catch (Exception e) { 4293 // just return nothing 4294 } 4295 } else { 4296 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); 4297 } 4298 return result; 4299 } 4300 4301 4302 private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4303 if (focus.size() == 0) { 4304 return new ArrayList<Base>(); 4305 } 4306 if (focus.size() != 1) { 4307 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size()); 4308 } 4309 Base base = focus.get(0); 4310 List<Base> result = new ArrayList<Base>(); 4311 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4312 Double d = Double.parseDouble(base.primitiveValue()); 4313 try { 4314 result.add(new DecimalType(Math.abs(d))); 4315 } catch (Exception e) { 4316 // just return nothing 4317 } 4318 } else if (base.hasType("Quantity")) { 4319 Quantity qty = makeQuantity(base); 4320 result.add(qty.copy().setValue(qty.getValue().abs())); 4321 } else { 4322 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal"); 4323 } 4324 return result; 4325 } 4326 4327 4328 private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4329 if (focus.size() == 0) { 4330 return new ArrayList<Base>(); 4331 } 4332 if (focus.size() != 1) { 4333 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size()); 4334 } 4335 Base base = focus.get(0); 4336 List<Base> result = new ArrayList<Base>(); 4337 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4338 Double d = Double.parseDouble(base.primitiveValue()); 4339 try {result.add(new IntegerType((int) Math.ceil(d))); 4340 } catch (Exception e) { 4341 // just return nothing 4342 } 4343 } else { 4344 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal"); 4345 } 4346 return result; 4347 } 4348 4349 private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4350 if (focus.size() == 0) { 4351 return new ArrayList<Base>(); 4352 } 4353 if (focus.size() != 1) { 4354 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size()); 4355 } 4356 Base base = focus.get(0); 4357 List<Base> result = new ArrayList<Base>(); 4358 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4359 Double d = Double.parseDouble(base.primitiveValue()); 4360 try { 4361 result.add(new IntegerType((int) Math.floor(d))); 4362 } catch (Exception e) { 4363 // just return nothing 4364 } 4365 } else { 4366 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal"); 4367 } 4368 return result; 4369 } 4370 4371 4372 private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4373 if (focus.size() == 0) { 4374 return new ArrayList<Base>(); 4375 } 4376 if (focus.size() > 1) { 4377 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size()); 4378 } 4379 Base base = focus.get(0); 4380 List<Base> result = new ArrayList<Base>(); 4381 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4382 Double d = Double.parseDouble(base.primitiveValue()); 4383 try { 4384 result.add(new DecimalType(Math.exp(d))); 4385 } catch (Exception e) { 4386 // just return nothing 4387 } 4388 4389 } else { 4390 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal"); 4391 } 4392 return result; 4393 } 4394 4395 4396 private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4397 if (focus.size() == 0) { 4398 return new ArrayList<Base>(); 4399 } 4400 if (focus.size() != 1) { 4401 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size()); 4402 } 4403 Base base = focus.get(0); 4404 List<Base> result = new ArrayList<Base>(); 4405 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4406 Double d = Double.parseDouble(base.primitiveValue()); 4407 try { 4408 result.add(new DecimalType(Math.log(d))); 4409 } catch (Exception e) { 4410 // just return nothing 4411 } 4412 } else { 4413 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal"); 4414 } 4415 return result; 4416 } 4417 4418 4419 private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4420 if (focus.size() == 0) { 4421 return new ArrayList<Base>(); 4422 } 4423 if (focus.size() != 1) { 4424 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size()); 4425 } 4426 Base base = focus.get(0); 4427 List<Base> result = new ArrayList<Base>(); 4428 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4429 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4430 if (n1.size() == 0) { 4431 // no base, so we just return nothing (as per the spec) 4432 return new ArrayList<Base>(); 4433 } 4434 if (n1.size() != 1) { 4435 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal"); 4436 } 4437 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4438 Double d = Double.parseDouble(base.primitiveValue()); 4439 try { 4440 result.add(new DecimalType(customLog(e, d))); 4441 } catch (Exception ex) { 4442 // just return nothing 4443 } 4444 } else { 4445 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal"); 4446 } 4447 return result; 4448 } 4449 4450 private static double customLog(double base, double logNumber) { 4451 return Math.log(logNumber) / Math.log(base); 4452 } 4453 4454 private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4455 if (focus.size() == 0) { 4456 return new ArrayList<Base>(); 4457 } 4458 if (focus.size() != 1) { 4459 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size()); 4460 } 4461 Base base = focus.get(0); 4462 List<Base> result = new ArrayList<Base>(); 4463 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4464 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4465 if (n1.size() == 0) { 4466 // no base, so we just return nothing (as per the spec) 4467 return new ArrayList<Base>(); 4468 } 4469 if (n1.size() != 1) { 4470 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal"); 4471 } 4472 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4473 Double d = Double.parseDouble(base.primitiveValue()); 4474 try { 4475 result.add(new DecimalType(Math.pow(d, e))); 4476 } catch (Exception ex) { 4477 // just return nothing 4478 } 4479 } else { 4480 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal"); 4481 } 4482 return result; 4483 } 4484 4485 private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4486 if (focus.size() == 0) { 4487 return new ArrayList<Base>(); 4488 } 4489 if (focus.size() != 1) { 4490 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size()); 4491 } 4492 Base base = focus.get(0); 4493 List<Base> result = new ArrayList<Base>(); 4494 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4495 String s = base.primitiveValue(); 4496 if (s.contains(".")) { 4497 s = s.substring(0, s.indexOf(".")); 4498 } 4499 result.add(new IntegerType(s)); 4500 } else { 4501 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal"); 4502 } 4503 return result; 4504 } 4505 4506 private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4507 if (focus.size() == 0) { 4508 return makeNull(); 4509 } 4510 if (focus.size() > 1) { 4511 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); 4512 } 4513 Integer precision = null; 4514 if (expr.getParameters().size() > 0) { 4515 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4516 if (n1.size() != 1) { 4517 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); 4518 } 4519 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4520 } 4521 4522 Base base = focus.get(0); 4523 List<Base> result = new ArrayList<Base>(); 4524 4525 if (base.hasType("decimal")) { 4526 if (precision == null || (precision >= 0 && precision < 17)) { 4527 result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4528 } 4529 } else if (base.hasType("integer")) { 4530 if (precision == null || (precision >= 0 && precision < 17)) { 4531 result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4532 } 4533 } else if (base.hasType("date")) { 4534 result.add(new DateTimeType(DateTimeUtil.lowBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); 4535 } else if (base.hasType("dateTime")) { 4536 result.add(new DateTimeType(DateTimeUtil.lowBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); 4537 } else if (base.hasType("time")) { 4538 result.add(new TimeType(DateTimeUtil.lowBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); 4539 } else if (base.hasType("Quantity")) { 4540 String value = getNamedValue(base, "value"); 4541 Base v = base.copy(); 4542 v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == null ? 8 : precision))); 4543 result.add(v); 4544 } else { 4545 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4546 } 4547 return result; 4548 } 4549 4550 private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4551 if (focus.size() == 0) { 4552 return makeNull(); 4553 } 4554 if (focus.size() > 1) { 4555 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4556 } 4557 Integer precision = null; 4558 if (expr.getParameters().size() > 0) { 4559 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4560 if (n1.size() != 1) { 4561 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer"); 4562 } 4563 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4564 } 4565 4566 4567 Base base = focus.get(0); 4568 List<Base> result = new ArrayList<Base>(); 4569 if (base.hasType("decimal")) { 4570 if (precision == null || (precision >= 0 && precision < 17)) { 4571 result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4572 } 4573 } else if (base.hasType("integer")) { 4574 if (precision == null || (precision >= 0 && precision < 17)) { 4575 result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision))); 4576 } 4577 } else if (base.hasType("date")) { 4578 result.add(new DateTimeType(DateTimeUtil.highBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision))); 4579 } else if (base.hasType("dateTime")) { 4580 result.add(new DateTimeType(DateTimeUtil.highBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision))); 4581 } else if (base.hasType("time")) { 4582 result.add(new TimeType(DateTimeUtil.highBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision))); 4583 } else if (base.hasType("Quantity")) { 4584 String value = getNamedValue(base, "value"); 4585 Base v = base.copy(); 4586 v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == null ? 8 : precision))); 4587 result.add(v); 4588 } else { 4589 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4590 } 4591 return result; 4592 } 4593 4594 private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4595 if (focus.size() == 0) { 4596 return new ArrayList<Base>(); 4597 } 4598 if (focus.size() != 1) { 4599 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4600 } 4601 Base base = focus.get(0); 4602 List<Base> result = new ArrayList<Base>(); 4603 if (base.hasType("decimal")) { 4604 result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue()))); 4605 } else if (base.hasType("date") || base.hasType("dateTime")) { 4606 result.add(new IntegerType(DateTimeUtil.getDatePrecision(base.primitiveValue()))); 4607 } else if (base.hasType("time")) { 4608 result.add(new IntegerType(DateTimeUtil.getTimePrecision(base.primitiveValue()))); 4609 } else { 4610 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date"); 4611 } 4612 return result; 4613 } 4614 4615 private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4616 if (focus.size() == 0) { 4617 return new ArrayList<Base>(); 4618 } 4619 if (focus.size() != 1) { 4620 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size()); 4621 } 4622 Base base = focus.get(0); 4623 List<Base> result = new ArrayList<Base>(); 4624 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4625 int i = 0; 4626 if (expr.getParameters().size() == 1) { 4627 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4628 if (n1.size() != 1) { 4629 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer"); 4630 } 4631 i = Integer.parseInt(n1.get(0).primitiveValue()); 4632 } 4633 BigDecimal d = new BigDecimal (base.primitiveValue()); 4634 result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP))); 4635 } else { 4636 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal"); 4637 } 4638 return result; 4639 } 4640 4641 private List<Base> funcDebug(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4642 String name = "<anonymous>"; 4643 if (expr.getParameters().size() == 1) { 4644 List<Base> n1 = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 4645 if (!n1.isEmpty()) { 4646 name =convertToString(n1); 4647 } 4648 } 4649 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", "); 4650 for (Base base : focus) { 4651 b.append(base.fhirType()+":"+convertToString(base)); 4652 } 4653 log.info("Debug Point '"+name+"': "+b.toString()); 4654 return focus; 4655 } 4656 4657 private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 4658 private ContextUtilities cu; 4659 public static String bytesToHex(byte[] bytes) { 4660 char[] hexChars = new char[bytes.length * 2]; 4661 for (int j = 0; j < bytes.length; j++) { 4662 int v = bytes[j] & 0xFF; 4663 hexChars[j * 2] = HEX_ARRAY[v >>> 4]; 4664 hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 4665 } 4666 return new String(hexChars); 4667 } 4668 4669 public static byte[] hexStringToByteArray(String s) { 4670 int len = s.length(); 4671 byte[] data = new byte[len / 2]; 4672 for (int i = 0; i < len; i += 2) { 4673 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); 4674 } 4675 return data; 4676 } 4677 4678 private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4679 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4680 String param = nl.get(0).primitiveValue(); 4681 4682 List<Base> result = new ArrayList<Base>(); 4683 4684 if (focus.size() == 1) { 4685 String cnt = focus.get(0).primitiveValue(); 4686 if ("hex".equals(param)) { 4687 result.add(new StringType(bytesToHex(cnt.getBytes()))); 4688 } else if ("base64".equals(param)) { 4689 Base64.Encoder enc = Base64.getEncoder(); 4690 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4691 } else if ("urlbase64".equals(param)) { 4692 Base64.Encoder enc = Base64.getUrlEncoder(); 4693 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4694 } 4695 } 4696 return result; 4697 } 4698 4699 private List<Base> funcDecode(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4700 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4701 String param = nl.get(0).primitiveValue(); 4702 4703 List<Base> result = new ArrayList<Base>(); 4704 if (focus.size() == 1) { 4705 String cnt = focus.get(0).primitiveValue(); 4706 if ("hex".equals(param)) { 4707 result.add(new StringType(new String(hexStringToByteArray(cnt)))); 4708 } else if ("base64".equals(param)) { 4709 Base64.Decoder enc = Base64.getDecoder(); 4710 result.add(new StringType(new String(enc.decode(cnt)))); 4711 } else if ("urlbase64".equals(param)) { 4712 Base64.Decoder enc = Base64.getUrlDecoder(); 4713 result.add(new StringType(new String(enc.decode(cnt)))); 4714 } 4715 } 4716 return result; 4717 } 4718 4719 private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4720 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4721 String param = nl.get(0).primitiveValue(); 4722 4723 List<Base> result = new ArrayList<Base>(); 4724 if (focus.size() == 1) { 4725 String cnt = focus.get(0).primitiveValue(); 4726 if ("html".equals(param)) { 4727 result.add(new StringType(Utilities.escapeXml(cnt))); 4728 } else if ("json".equals(param)) { 4729 result.add(new StringType(Utilities.escapeJson(cnt))); 4730 } else if ("url".equals(param)) { 4731 result.add(new StringType(Utilities.URLEncode(cnt))); 4732 } else if ("md".equals(param)) { 4733 result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt))); 4734 } 4735 } 4736 4737 return result; 4738 } 4739 4740 private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4741 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4742 String param = nl.get(0).primitiveValue(); 4743 4744 List<Base> result = new ArrayList<Base>(); 4745 if (focus.size() == 1) { 4746 String cnt = focus.get(0).primitiveValue(); 4747 if ("html".equals(param)) { 4748 result.add(new StringType(Utilities.unescapeXml(cnt))); 4749 } else if ("json".equals(param)) { 4750 result.add(new StringType(Utilities.unescapeJson(cnt))); 4751 } else if ("url".equals(param)) { 4752 result.add(new StringType(Utilities.URLDecode(cnt))); 4753 } else if ("md".equals(param)) { 4754 result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt))); 4755 } 4756 } 4757 4758 return result; 4759 } 4760 4761 private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4762 List<Base> result = new ArrayList<Base>(); 4763 if (focus.size() == 1) { 4764 String cnt = focus.get(0).primitiveValue(); 4765 result.add(new StringType(cnt.trim())); 4766 } 4767 return result; 4768 } 4769 4770 private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4771 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4772 String param = nl.get(0).primitiveValue(); 4773 4774 List<Base> result = new ArrayList<Base>(); 4775 if (focus.size() == 1) { 4776 String cnt = focus.get(0).primitiveValue(); 4777 String[] sl = Utilities.simpleSplit(cnt, param); 4778 for (String s : sl) { 4779 result.add(new StringType(s)); 4780 } 4781 } 4782 return result; 4783 } 4784 4785 private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4786 List<Base> nl = exp.getParameters().size() > 0 ? execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true) : new ArrayList<Base>(); 4787 String param = ""; 4788 String param2 = ""; 4789 if (exp.getParameters().size() > 0) { 4790 param = nl.get(0).primitiveValue(); 4791 param2 = param; 4792 if (exp.getParameters().size() == 2) { 4793 nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(1), true); 4794 param2 = nl.get(0).primitiveValue(); 4795 } 4796 } 4797 4798 List<Base> result = new ArrayList<Base>(); 4799 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2); 4800 for (Base i : focus) { 4801 b.append(i.primitiveValue()); 4802 } 4803 result.add(new StringType(b.toString())); 4804 return result; 4805 } 4806 4807 private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4808 // todo: actually check the HTML 4809 if (focus.size() != 1) { 4810 return makeBoolean(false); 4811 } 4812 XhtmlNode x = focus.get(0).getXhtml(); 4813 if (x == null) { 4814 return makeBoolean(false); 4815 } 4816 boolean ok = checkHtmlNames(x, true); 4817 if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) { 4818 ok = checkForContent(x); 4819 } 4820 return makeBoolean(ok); 4821 } 4822 4823 private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4824 // todo: actually check the HTML 4825 if (focus.size() != 1) { 4826 return makeBoolean(false); 4827 } 4828 XhtmlNode x = focus.get(0).getXhtml(); 4829 if (x == null) { 4830 return makeBoolean(false); 4831 } 4832 return makeBoolean(checkForContent(x)); 4833 } 4834 4835 private boolean checkForContent(XhtmlNode x) { 4836 if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) { 4837 return true; 4838 } 4839 for (XhtmlNode c : x.getChildNodes()) { 4840 if (checkForContent(c)) { 4841 return true; 4842 } 4843 } 4844 return false; 4845 } 4846 4847 private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4848 // Undocumented what this should do if the input collection is empty 4849 // if (focus.size() == 0) { 4850 // return new ArrayList<Base>(); 4851 // } 4852 if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) { 4853 return makeBoolean(false); 4854 } 4855 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 4856 if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) { 4857 return makeBoolean(false); 4858 } 4859 String s1 = getNamedValue(focus.get(0), "system"); 4860 String u1 = getNamedValue(focus.get(0), "code"); 4861 String s2 = getNamedValue(nl.get(0), "system"); 4862 String u2 = getNamedValue(nl.get(0), "code"); 4863 4864 if (s1 == null || s2 == null || !s1.equals(s2)) { 4865 return makeBoolean(false); 4866 } 4867 if (u1 == null || u2 == null) { 4868 return makeBoolean(false); 4869 } 4870 if (u1.equals(u2)) { 4871 return makeBoolean(true); 4872 } 4873 if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) { 4874 try { 4875 return makeBoolean(worker.getUcumService().isComparable(u1, u2)); 4876 } catch (UcumException e) { 4877 return makeBoolean(false); 4878 } 4879 } else { 4880 return makeBoolean(false); 4881 } 4882 } 4883 4884 4885 private String getNamedValue(Base base, String name) { 4886 Property p = base.getChildByName(name); 4887 if (p != null && p.hasValues() && p.getValues().size() == 1) { 4888 return p.getValues().get(0).primitiveValue(); 4889 } 4890 return null; 4891 } 4892 4893 private boolean checkHtmlNames(XhtmlNode node, boolean block) { 4894 if (node.getNodeType() == NodeType.Comment) { 4895 if (node.getContent().startsWith("DOCTYPE")) 4896 return false; 4897 } 4898 if (node.getNodeType() == NodeType.Element) { 4899 if (block) { 4900 if (!Utilities.existsInList(node.getName(), 4901 "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", 4902 "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", 4903 "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", 4904 "code", "samp", "img", "map", "area")) { 4905 return false; 4906 } 4907 } else { 4908 if (!Utilities.existsInList(node.getName(), 4909 "a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) { 4910 return false; 4911 } 4912 } 4913 for (String an : node.getAttributes().keySet()) { 4914 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 4915 "title", "style", "class", "id", "lang", "xml:lang", "xml:space", "dir", "accesskey", "tabindex", 4916 // tables 4917 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || 4918 4919 Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite", 4920 "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src", 4921 "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape", 4922 "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border", 4923 "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap" 4924 ); 4925 if (!ok) { 4926 return false; 4927 } 4928 } 4929 for (XhtmlNode c : node.getChildNodes()) { 4930 if (!checkHtmlNames(c, block && !"p".equals(c))) { 4931 return false; 4932 } 4933 } 4934 } 4935 return true; 4936 } 4937 4938 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4939 List<Base> result = new ArrayList<Base>(); 4940 if (exp.getParameters().size() == 1) { 4941 List<Base> pc = new ArrayList<Base>(); 4942 boolean all = true; 4943 for (Base item : focus) { 4944 pc.clear(); 4945 pc.add(item); 4946 Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 4947 if (eq != Equality.True) { 4948 all = false; 4949 break; 4950 } 4951 } 4952 result.add(new BooleanType(all).noExtensions()); 4953 } else {// (exp.getParameters().size() == 0) { 4954 boolean all = true; 4955 for (Base item : focus) { 4956 Equality eq = asBool(item, true); 4957 if (eq != Equality.True) { 4958 all = false; 4959 break; 4960 } 4961 } 4962 result.add(new BooleanType(all).noExtensions()); 4963 } 4964 return result; 4965 } 4966 4967 4968 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 4969 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis); 4970 // append all of the defined variables from the context into the new context 4971 if (context.definedVariables != null) { 4972 for (String s : context.definedVariables.keySet()) { 4973 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4974 } 4975 } 4976 return newContext; 4977 } 4978 4979 private ExecutionContext contextForParameter(ExecutionContext context) { 4980 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem); 4981 newContext.total = context.total; 4982 newContext.index = context.index; 4983 // append all of the defined variables from the context into the new context 4984 if (context.definedVariables != null) { 4985 for (String s : context.definedVariables.keySet()) { 4986 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4987 } 4988 } 4989 return newContext; 4990 } 4991 4992 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 4993 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.rootResource, context.resource, context.context, newThis); 4994 // append all of the defined variables from the context into the new context 4995 if (context.definedVariables != null) { 4996 for (String s : context.definedVariables.keySet()) { 4997 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4998 } 4999 } 5000 return newContext; 5001 } 5002 5003 private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) { 5004 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.rootResource, context.resource, context.context, context.thisItem); 5005 // append all of the defined variables from the context into the new context 5006 if (context.definedVariables != null) { 5007 for (String s : context.definedVariables.keySet()) { 5008 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 5009 } 5010 } 5011 return newContext; 5012 } 5013 5014 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5015 List<Base> result = new ArrayList<Base>(); 5016 result.add(DateTimeType.now()); 5017 return result; 5018 } 5019 5020 5021 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5022 List<Base> result = new ArrayList<Base>(); 5023 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 5024 return result; 5025 } 5026 5027 5028 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5029 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5030 if (nl.size() != 1 || focus.size() != 1) { 5031 return new ArrayList<Base>(); 5032 } 5033 5034 String url = nl.get(0).primitiveValue(); 5035 ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url); 5036 if (vs == null) { 5037 return new ArrayList<Base>(); 5038 } 5039 Base l = focus.get(0); 5040 if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { 5041 return makeBoolean(worker.validateCode(terminologyServiceOptions.withGuessSystem(), TypeConvertor.castToCoding(l), vs).isOk()); 5042 } else if (l.fhirType().equals("Coding")) { 5043 return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk()); 5044 } else if (l.fhirType().equals("CodeableConcept")) { 5045 return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk()); 5046 } else { 5047 return new ArrayList<Base>(); 5048 } 5049 } 5050 5051 5052 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5053 List<Base> result = new ArrayList<Base>(); 5054 List<Base> current = new ArrayList<Base>(); 5055 current.addAll(focus); 5056 List<Base> added = new ArrayList<Base>(); 5057 boolean more = true; 5058 while (more) { 5059 added.clear(); 5060 for (Base item : current) { 5061 getChildrenByName(item, "*", added); 5062 } 5063 more = !added.isEmpty(); 5064 result.addAll(added); 5065 current.clear(); 5066 current.addAll(added); 5067 } 5068 return result; 5069 } 5070 5071 5072 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5073 List<Base> result = new ArrayList<Base>(); 5074 for (Base b : focus) { 5075 getChildrenByName(b, "*", result); 5076 } 5077 return result; 5078 } 5079 5080 5081 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException { 5082 List<Base> result = new ArrayList<Base>(); 5083 List<Base> tB = execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true); 5084 String t = convertToString(tB); 5085 List<Base> rB = execute(context, baseToList(context.thisItem), expr.getParameters().get(1), true); 5086 String r = convertToString(rB); 5087 5088 if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) { 5089 // 5090 } else if (focus.size() == 1) { 5091 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5092 String f = convertToString(focus.get(0)); 5093 if (Utilities.noString(f)) { 5094 result.add(new StringType("")); 5095 } else { 5096 String n = f.replace(t, r); 5097 result.add(new StringType(n)); 5098 } 5099 } 5100 } else { 5101 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size()); 5102 } 5103 return result; 5104 } 5105 5106 5107 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5108 List<Base> result = new ArrayList<Base>(); 5109 List<Base> regexB = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5110 String regex = convertToString(regexB); 5111 List<Base> replB = execute(context, baseToList(context.thisItem), exp.getParameters().get(1), true); 5112 String repl = convertToString(replB); 5113 5114 if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) { 5115 // 5116 } else if (focus.size() == 1 && !Utilities.noString(regex)) { 5117 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5118 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); 5119 } 5120 } else { 5121 result.add(new StringType(convertToString(focus.get(0))).noExtensions()); 5122 } 5123 return result; 5124 } 5125 5126 5127 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5128 List<Base> result = new ArrayList<Base>(); 5129 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5130 String sw = convertToString(swb); 5131 5132 if (focus.size() == 0) { 5133 // 5134 } else if (swb.size() == 0) { 5135 // 5136 } else if (Utilities.noString(sw)) { 5137 result.add(new BooleanType(true).noExtensions()); 5138 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5139 if (focus.size() == 1 && !Utilities.noString(sw)) { 5140 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 5141 } else { 5142 result.add(new BooleanType(false).noExtensions()); 5143 } 5144 } 5145 return result; 5146 } 5147 5148 5149 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5150 List<Base> result = new ArrayList<Base>(); 5151 for (Base item : focus) { 5152 String value = convertToString(item); 5153 if (value != null) 5154 result.add(new StringType(value).noExtensions()); 5155 } 5156 5157 if (result.size() > 1) { 5158 throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "toString", result.size()); 5159 } 5160 return result; 5161 } 5162 5163 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5164 List<Base> result = new ArrayList<Base>(); 5165 if (focus.size() == 1) { 5166 if (focus.get(0) instanceof BooleanType) { 5167 result.add(focus.get(0)); 5168 } else if (focus.get(0) instanceof IntegerType) { 5169 int i = Integer.parseInt(focus.get(0).primitiveValue()); 5170 if (i == 0) { 5171 result.add(new BooleanType(false).noExtensions()); 5172 } else if (i == 1) { 5173 result.add(new BooleanType(true).noExtensions()); 5174 } 5175 } else if (focus.get(0) instanceof DecimalType) { 5176 if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) { 5177 result.add(new BooleanType(false).noExtensions()); 5178 } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) { 5179 result.add(new BooleanType(true).noExtensions()); 5180 } 5181 } else if (focus.get(0) instanceof StringType) { 5182 if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) { 5183 result.add(new BooleanType(true).noExtensions()); 5184 } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) { 5185 result.add(new BooleanType(false).noExtensions()); 5186 } 5187 } 5188 } 5189 return result; 5190 } 5191 5192 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5193 // THIS DOESN'T APPEAR TO BE PROCESSING THE UNITS 5194 List<Base> result = new ArrayList<Base>(); 5195 if (focus.size() == 1) { 5196 var qty = makeQuantity(focus.get(0)); 5197 if (qty instanceof Quantity) { 5198 result.add(qty); 5199 } else if (focus.get(0) instanceof StringType) { 5200 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 5201 if (q != null) { 5202 result.add(q.noExtensions()); 5203 } 5204 } else if (focus.get(0) instanceof IntegerType) { 5205 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 5206 } else if (focus.get(0) instanceof DecimalType) { 5207 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 5208 } 5209 } 5210 return result; 5211 } 5212 5213 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5214 // List<Base> result = new ArrayList<Base>(); 5215 // result.add(new BooleanType(convertToBoolean(focus))); 5216 // return result; 5217 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime"); 5218 } 5219 5220 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5221 // List<Base> result = new ArrayList<Base>(); 5222 // result.add(new BooleanType(convertToBoolean(focus))); 5223 // return result; 5224 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime"); 5225 } 5226 5227 5228 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5229 String s = convertToString(focus); 5230 List<Base> result = new ArrayList<Base>(); 5231 if (Utilities.isDecimal(s, true)) { 5232 result.add(new DecimalType(s).noExtensions()); 5233 } 5234 if ("true".equals(s)) { 5235 result.add(new DecimalType(1).noExtensions()); 5236 } 5237 if ("false".equals(s)) { 5238 result.add(new DecimalType(0).noExtensions()); 5239 } 5240 return result; 5241 } 5242 5243 5244 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5245 if (focus.size() > 1) { 5246 throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size()); 5247 } 5248 5249 List<Base> n1 = execute(focus.isEmpty() ? context : changeThis(context, focus.get(0)), focus, exp.getParameters().get(0), true); 5250 Equality v = asBool(n1, exp); 5251 if (v == Equality.True) { 5252 return execute(context, focus, exp.getParameters().get(1), true); 5253 } else if (exp.getParameters().size() < 3) { 5254 return new ArrayList<Base>(); 5255 } else { 5256 return execute(context, focus, exp.getParameters().get(2), true); 5257 } 5258 } 5259 5260 5261 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5262 List<Base> n1 = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5263 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 5264 5265 List<Base> result = new ArrayList<Base>(); 5266 for (int i = 0; i < Math.min(focus.size(), i1); i++) { 5267 result.add(focus.get(i)); 5268 } 5269 return result; 5270 } 5271 5272 5273 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5274 List<Base> result = new ArrayList<Base>(); 5275 for (Base item : focus) { 5276 if (!doContains(result, item)) { 5277 result.add(item); 5278 } 5279 } 5280 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 5281 if (!doContains(result, item)) { 5282 result.add(item); 5283 } 5284 } 5285 return result; 5286 } 5287 5288 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5289 List<Base> result = new ArrayList<Base>(); 5290 for (Base item : focus) { 5291 result.add(item); 5292 } 5293 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 5294 result.add(item); 5295 } 5296 return result; 5297 } 5298 5299 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5300 List<Base> result = new ArrayList<Base>(); 5301 List<Base> other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5302 5303 for (Base item : focus) { 5304 if (!doContains(result, item) && doContains(other, item)) { 5305 result.add(item); 5306 } 5307 } 5308 return result; 5309 } 5310 5311 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5312 List<Base> result = new ArrayList<Base>(); 5313 List<Base> other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5314 5315 for (Base item : focus) { 5316 if (!doContains(other, item)) { 5317 result.add(item); 5318 } 5319 } 5320 return result; 5321 } 5322 5323 5324 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException { 5325 if (focus.size() == 1) { 5326 return focus; 5327 } 5328 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size()); 5329 } 5330 5331 5332 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException { 5333 if (focus.size() == 0 || focus.size() > 1) { 5334 return makeNull(); 5335 } 5336 String ns = null; 5337 String n = null; 5338 5339 ExpressionNode texp = expr.getParameters().get(0); 5340 if (texp.getKind() != Kind.Name) { 5341 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is"); 5342 } 5343 if (texp.getInner() != null) { 5344 if (texp.getInner().getKind() != Kind.Name) { 5345 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is"); 5346 } 5347 ns = texp.getName(); 5348 n = texp.getInner().getName(); 5349 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) { 5350 ns = "System"; 5351 n = texp.getName(); 5352 } else { 5353 ns = "FHIR"; 5354 n = texp.getName(); 5355 } 5356 if (ns.equals("System")) { 5357 if (focus.get(0) instanceof Resource) { 5358 return makeBoolean(false); 5359 } 5360 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) { 5361 String t = Utilities.capitalize(focus.get(0).fhirType()); 5362 if (n.equals(t)) { 5363 return makeBoolean(true); 5364 } 5365 if ("Date".equals(t) && n.equals("DateTime")) { 5366 return makeBoolean(true); 5367 } else { 5368 return makeBoolean(false); 5369 } 5370 } else { 5371 return makeBoolean(false); 5372 } 5373 } else if (ns.equals("FHIR")) { 5374 if (n.equals(focus.get(0).fhirType())) { 5375 return makeBoolean(true); 5376 } else { 5377 StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); 5378 while (sd != null) { 5379 if (n.equals(sd.getType())) { 5380 return makeBoolean(true); 5381 } 5382 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5383 } 5384 return makeBoolean(false); 5385 } 5386 } else { 5387 return makeBoolean(false); 5388 } 5389 } 5390 5391 5392 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5393 List<Base> result = new ArrayList<Base>(); 5394 String tn; 5395 if (expr.getParameters().get(0).getInner() != null) { 5396 tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); 5397 } else { 5398 tn = "FHIR."+expr.getParameters().get(0).getName(); 5399 } 5400 if (!isKnownType(tn)) { 5401 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 5402 } 5403 if (!doNotEnforceAsSingletonRule && focus.size() > 1) { 5404 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 5405 } 5406 5407 for (Base b : focus) { 5408 if (tn.startsWith("System.")) { 5409 if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 5410 if (b.hasType(tn.substring(7))) { 5411 result.add(b); 5412 } 5413 } 5414 5415 } else if (tn.startsWith("FHIR.")) { 5416 String tnp = tn.substring(5); 5417 if (b.fhirType().equals(tnp)) { 5418 result.add(b); 5419 } else { 5420 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5421 while (sd != null) { 5422 if (compareTypeNames(tnp, sd.getType())) { 5423 result.add(b); 5424 break; 5425 } 5426 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5427 } 5428 } 5429 } 5430 } 5431 return result; 5432 } 5433 5434 5435 private List<Base> funcOfType(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5436 List<Base> result = new ArrayList<Base>(); 5437 String tn; 5438 if (expr.getParameters().get(0).getInner() != null) { 5439 tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName(); 5440 } else { 5441 tn = "FHIR."+expr.getParameters().get(0).getName(); 5442 } 5443 if (!isKnownType(tn)) { 5444 throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 5445 } 5446 5447 5448 for (Base b : focus) { 5449 if (tn.startsWith("System.")) { 5450 if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 5451 if (b.hasType(tn.substring(7))) { 5452 result.add(b); 5453 } 5454 } 5455 5456 } else if (tn.startsWith("FHIR.")) { 5457 String tnp = tn.substring(5); 5458 if (b.fhirType().equals(tnp)) { 5459 result.add(b); 5460 } else { 5461 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5462 while (sd != null) { 5463 if (tnp.equals(sd.getType())) { 5464 result.add(b); 5465 break; 5466 } 5467 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5468 } 5469 } 5470 } else if (tn.startsWith("CDA.")) { 5471 String tnp = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4)); 5472 if (b.fhirType().equals(tnp)) { 5473 result.add(b); 5474 } else { 5475 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5476 while (sd != null) { 5477 if (tnp.equals(sd.getType())) { 5478 result.add(b); 5479 break; 5480 } 5481 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 5482 } 5483 } 5484 } 5485 } 5486 return result; 5487 } 5488 5489 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5490 List<Base> result = new ArrayList<Base>(); 5491 for (Base item : focus) { 5492 result.add(new ClassTypeInfo(item)); 5493 } 5494 return result; 5495 } 5496 5497 5498 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5499 List<Base> result = new ArrayList<Base>(); 5500 List<Base> current = new ArrayList<Base>(); 5501 current.addAll(focus); 5502 List<Base> added = new ArrayList<Base>(); 5503 boolean more = true; 5504 while (more) { 5505 added.clear(); 5506 List<Base> pc = new ArrayList<Base>(); 5507 for (Base item : current) { 5508 pc.clear(); 5509 pc.add(item); 5510 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 5511 } 5512 more = false; 5513 current.clear(); 5514 for (Base b : added) { 5515 boolean isnew = true; 5516 for (Base t : result) { 5517 if (b.equalsDeep(t)) { 5518 isnew = false; 5519 } 5520 } 5521 if (isnew) { 5522 result.add(b); 5523 current.add(b); 5524 more = true; 5525 } 5526 } 5527 } 5528 return result; 5529 } 5530 5531 5532 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5533 List<Base> total = new ArrayList<Base>(); 5534 if (exp.parameterCount() > 1) { 5535 total = execute(context, focus, exp.getParameters().get(1), false); 5536 } 5537 5538 List<Base> pc = new ArrayList<Base>(); 5539 for (Base item : focus) { 5540 ExecutionContext c = changeThis(context, item); 5541 c.total = total; 5542 c.next(); 5543 total = execute(c, pc, exp.getParameters().get(0), true); 5544 } 5545 return total; 5546 } 5547 5548 5549 5550 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5551 if (focus.size() < 1) { 5552 return makeBoolean(true); 5553 } 5554 if (focus.size() == 1) { 5555 return makeBoolean(true); 5556 } 5557 5558 boolean distinct = true; 5559 for (int i = 0; i < focus.size(); i++) { 5560 for (int j = i+1; j < focus.size(); j++) { 5561 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5562 if (eq == null) { 5563 return new ArrayList<Base>(); 5564 } else if (eq == true) { 5565 distinct = false; 5566 break; 5567 } 5568 } 5569 } 5570 return makeBoolean(distinct); 5571 } 5572 5573 5574 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5575 List<Base> target = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5576 5577 boolean valid = true; 5578 for (Base item : target) { 5579 boolean found = false; 5580 for (Base t : focus) { 5581 if (Base.compareDeep(item, t, false)) { 5582 found = true; 5583 break; 5584 } 5585 } 5586 if (!found) { 5587 valid = false; 5588 break; 5589 } 5590 } 5591 List<Base> result = new ArrayList<Base>(); 5592 result.add(new BooleanType(valid).noExtensions()); 5593 return result; 5594 } 5595 5596 5597 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5598 List<Base> target = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5599 5600 boolean valid = true; 5601 for (Base item : focus) { 5602 boolean found = false; 5603 for (Base t : target) { 5604 if (Base.compareDeep(item, t, false)) { 5605 found = true; 5606 break; 5607 } 5608 } 5609 if (!found) { 5610 valid = false; 5611 break; 5612 } 5613 } 5614 List<Base> result = new ArrayList<Base>(); 5615 result.add(new BooleanType(valid).noExtensions()); 5616 return result; 5617 } 5618 5619 5620 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5621 List<Base> result = new ArrayList<Base>(); 5622 boolean empty = true; 5623 List<Base> pc = new ArrayList<Base>(); 5624 for (Base f : focus) { 5625 if (exp.getParameters().size() == 1) { 5626 pc.clear(); 5627 pc.add(f); 5628 Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp); 5629 if (v == Equality.True) { 5630 empty = false; 5631 } 5632 } else if (!f.isEmpty()) { 5633 empty = false; 5634 } 5635 } 5636 result.add(new BooleanType(!empty).noExtensions()); 5637 return result; 5638 } 5639 5640 5641 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5642 List<Base> result = new ArrayList<Base>(); 5643 Base refContext = null; 5644 for (Base item : focus) { 5645 String s = convertToString(item); 5646 if (item.fhirType().equals("Reference")) { 5647 refContext = item; 5648 Property p = item.getChildByName("reference"); 5649 if (p != null && p.hasValues()) { 5650 s = convertToString(p.getValues().get(0)); 5651 } else { 5652 s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) 5653 } 5654 } 5655 if (item.fhirType().equals("canonical")) { 5656 s = item.primitiveValue(); 5657 refContext = item; 5658 } 5659 if (s != null) { 5660 Base res = null; 5661 if (s.startsWith("#")) { 5662 String t = s.substring(1); 5663 Property p = context.rootResource.getChildByName("contained"); 5664 if (p != null) { 5665 for (Base c : p.getValues()) { 5666 if (t.equals(c.getIdBase())) { 5667 res = c; 5668 break; 5669 } 5670 } 5671 } 5672 } else if (hostServices != null) { 5673 try { 5674 res = hostServices.resolveReference(this, context.appInfo, s, refContext); 5675 } catch (Exception e) { 5676 res = null; 5677 } 5678 } 5679 if (res != null) { 5680 result.add(res); 5681 } 5682 } 5683 } 5684 5685 return result; 5686 } 5687 5688 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5689 List<Base> result = new ArrayList<Base>(); 5690 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5691 String url = nl.get(0).primitiveValue(); 5692 5693 for (Base item : focus) { 5694 List<Base> ext = new ArrayList<Base>(); 5695 getChildrenByName(item, "extension", ext); 5696 getChildrenByName(item, "modifierExtension", ext); 5697 for (Base ex : ext) { 5698 List<Base> vl = new ArrayList<Base>(); 5699 getChildrenByName(ex, "url", vl); 5700 if (convertToString(vl).equals(url)) { 5701 result.add(ex); 5702 } 5703 } 5704 } 5705 return result; 5706 } 5707 5708 private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5709 List<Base> result = new ArrayList<Base>(); 5710 if (exp.getParameters().size() == 1) { 5711 boolean all = true; 5712 List<Base> pc = new ArrayList<Base>(); 5713 for (Base item : focus) { 5714 pc.clear(); 5715 pc.add(item); 5716 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5717 Equality v = asBool(res, exp); 5718 if (v != Equality.False) { 5719 all = false; 5720 break; 5721 } 5722 } 5723 result.add(new BooleanType(all).noExtensions()); 5724 } else { 5725 boolean all = true; 5726 for (Base item : focus) { 5727 if (!canConvertToBoolean(item)) { 5728 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5729 } 5730 5731 Equality v = asBool(item, true); 5732 if (v != Equality.False) { 5733 all = false; 5734 break; 5735 } 5736 } 5737 result.add(new BooleanType(all).noExtensions()); 5738 } 5739 return result; 5740 } 5741 5742 private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5743 List<Base> result = new ArrayList<Base>(); 5744 if (exp.getParameters().size() == 1) { 5745 boolean any = false; 5746 List<Base> pc = new ArrayList<Base>(); 5747 for (Base item : focus) { 5748 pc.clear(); 5749 pc.add(item); 5750 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5751 Equality v = asBool(res, exp); 5752 if (v == Equality.False) { 5753 any = true; 5754 break; 5755 } 5756 } 5757 result.add(new BooleanType(any).noExtensions()); 5758 } else { 5759 boolean any = false; 5760 for (Base item : focus) { 5761 if (!canConvertToBoolean(item)) { 5762 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5763 } 5764 5765 Equality v = asBool(item, true); 5766 if (v == Equality.False) { 5767 any = true; 5768 break; 5769 } 5770 } 5771 result.add(new BooleanType(any).noExtensions()); 5772 } 5773 return result; 5774 } 5775 5776 private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5777 List<Base> result = new ArrayList<Base>(); 5778 if (exp.getParameters().size() == 1) { 5779 boolean all = true; 5780 List<Base> pc = new ArrayList<Base>(); 5781 for (Base item : focus) { 5782 pc.clear(); 5783 pc.add(item); 5784 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5785 Equality v = asBool(res, exp); 5786 if (v != Equality.True) { 5787 all = false; 5788 break; 5789 } 5790 } 5791 result.add(new BooleanType(all).noExtensions()); 5792 } else { 5793 boolean all = true; 5794 for (Base item : focus) { 5795 if (!canConvertToBoolean(item)) { 5796 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5797 } 5798 Equality v = asBool(item, true); 5799 if (v != Equality.True) { 5800 all = false; 5801 break; 5802 } 5803 } 5804 result.add(new BooleanType(all).noExtensions()); 5805 } 5806 return result; 5807 } 5808 5809 private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5810 List<Base> result = new ArrayList<Base>(); 5811 if (exp.getParameters().size() == 1) { 5812 boolean any = false; 5813 List<Base> pc = new ArrayList<Base>(); 5814 for (Base item : focus) { 5815 pc.clear(); 5816 pc.add(item); 5817 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5818 Equality v = asBool(res, exp); 5819 if (v == Equality.True) { 5820 any = true; 5821 break; 5822 } 5823 } 5824 result.add(new BooleanType(any).noExtensions()); 5825 } else { 5826 boolean any = false; 5827 for (Base item : focus) { 5828 if (!canConvertToBoolean(item)) { 5829 throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean"); 5830 } 5831 5832 Equality v = asBool(item, true); 5833 if (v == Equality.True) { 5834 any = true; 5835 break; 5836 } 5837 } 5838 result.add(new BooleanType(any).noExtensions()); 5839 } 5840 return result; 5841 } 5842 5843 private boolean canConvertToBoolean(Base item) { 5844 return (item.isBooleanPrimitive()); 5845 } 5846 5847 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5848 List<Base> nl = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5849 String name = nl.get(0).primitiveValue(); 5850 if (exp.getParameters().size() == 2) { 5851 List<Base> n2 = execute(context, baseToList(context.thisItem), exp.getParameters().get(1), true); 5852 addToLog(name, n2); 5853 } else { 5854 addToLog(name, focus); 5855 } 5856 return focus; 5857 } 5858 5859 private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5860 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5861 String name = nl.get(0).primitiveValue(); 5862 List<Base> value; 5863 if (exp.getParameters().size() == 2) { 5864 value = execute(context, focus, exp.getParameters().get(1), true); 5865 } else { 5866 value = focus; 5867 } 5868 // stash the variable into the context 5869 context.setDefinedVariable(name, value); 5870 return focus; 5871 } 5872 5873 private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { 5874 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 5875 if (!convertToBoolean(n1)) { 5876 List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true); 5877 String name = n2.get(0).primitiveValue(); 5878 throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name); 5879 } 5880 return focus; 5881 } 5882 5883 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5884 if (focus.size() <= 1) { 5885 return focus; 5886 } 5887 5888 List<Base> result = new ArrayList<Base>(); 5889 for (int i = 0; i < focus.size(); i++) { 5890 boolean found = false; 5891 for (int j = i+1; j < focus.size(); j++) { 5892 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5893 if (eq == null) 5894 return new ArrayList<Base>(); 5895 else if (eq == true) { 5896 found = true; 5897 break; 5898 } 5899 } 5900 if (!found) { 5901 result.add(focus.get(i)); 5902 } 5903 } 5904 return result; 5905 } 5906 5907 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5908 List<Base> result = new ArrayList<Base>(); 5909 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5910 String sw = convertToString(swb); 5911 5912 if (focus.size() == 0 || swb.size() == 0) { 5913 // 5914 } else if (focus.size() == 1 && !Utilities.noString(sw)) { 5915 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5916 String st = convertToString(focus.get(0)); 5917 if (Utilities.noString(st)) { 5918 result.add(new BooleanType(false).noExtensions()); 5919 } else { 5920 Pattern p = Pattern.compile("(?s)" + sw); 5921 Matcher m = p.matcher(st); 5922 boolean ok = m.find(); 5923 result.add(new BooleanType(ok).noExtensions()); 5924 } 5925 } 5926 } else { 5927 result.add(new BooleanType(false).noExtensions()); 5928 } 5929 return result; 5930 } 5931 5932 private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5933 List<Base> result = new ArrayList<Base>(); 5934 String sw = convertToString(execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)); 5935 5936 if (focus.size() == 1 && !Utilities.noString(sw)) { 5937 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5938 String st = convertToString(focus.get(0)); 5939 if (Utilities.noString(st)) { 5940 result.add(new BooleanType(false).noExtensions()); 5941 } else { 5942 Pattern p = Pattern.compile("(?s)" + sw); 5943 Matcher m = p.matcher(st); 5944 boolean ok = m.matches(); 5945 result.add(new BooleanType(ok).noExtensions()); 5946 } 5947 } 5948 } else { 5949 result.add(new BooleanType(false).noExtensions()); 5950 } 5951 return result; 5952 } 5953 5954 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5955 List<Base> result = new ArrayList<Base>(); 5956 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5957 String sw = convertToString(swb); 5958 5959 if (focus.size() != 1) { 5960 // 5961 } else if (swb.size() != 1) { 5962 // 5963 } else if (Utilities.noString(sw)) { 5964 result.add(new BooleanType(true).noExtensions()); 5965 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5966 String st = convertToString(focus.get(0)); 5967 if (Utilities.noString(st)) { 5968 result.add(new BooleanType(false).noExtensions()); 5969 } else { 5970 result.add(new BooleanType(st.contains(sw)).noExtensions()); 5971 } 5972 } 5973 return result; 5974 } 5975 5976 private List<Base> baseToList(Base b) { 5977 List<Base> res = new ArrayList<>(); 5978 res.add(b); 5979 return res; 5980 } 5981 5982 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5983 List<Base> result = new ArrayList<Base>(); 5984 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5985 String s = convertToString(focus.get(0)); 5986 result.add(new IntegerType(s.length()).noExtensions()); 5987 } 5988 return result; 5989 } 5990 5991 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5992 List<Base> result = new ArrayList<Base>(); 5993 if (focus.size() == 1) { 5994 String s = convertToString(focus.get(0)); 5995 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 5996 } else { 5997 result.add(new BooleanType(false).noExtensions()); 5998 } 5999 return result; 6000 } 6001 6002 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6003 List<Base> result = new ArrayList<Base>(); 6004 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 6005 String sw = convertToString(swb); 6006 6007 if (focus.size() == 0) { 6008 // no result 6009 } else if (swb.size() == 0) { 6010 // no result 6011 } else if (Utilities.noString(sw)) { 6012 result.add(new BooleanType(true).noExtensions()); 6013 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 6014 String s = convertToString(focus.get(0)); 6015 if (s == null) { 6016 result.add(new BooleanType(false).noExtensions()); 6017 } else { 6018 result.add(new BooleanType(s.startsWith(sw)).noExtensions()); 6019 } 6020 } 6021 return result; 6022 } 6023 6024 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6025 List<Base> result = new ArrayList<Base>(); 6026 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 6027 String s = convertToString(focus.get(0)); 6028 if (!Utilities.noString(s)) { 6029 result.add(new StringType(s.toLowerCase()).noExtensions()); 6030 } 6031 } 6032 return result; 6033 } 6034 6035 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6036 List<Base> result = new ArrayList<Base>(); 6037 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 6038 String s = convertToString(focus.get(0)); 6039 if (!Utilities.noString(s)) { 6040 result.add(new StringType(s.toUpperCase()).noExtensions()); 6041 } 6042 } 6043 return result; 6044 } 6045 6046 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6047 List<Base> result = new ArrayList<Base>(); 6048 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 6049 String s = convertToString(focus.get(0)); 6050 for (char c : s.toCharArray()) { 6051 result.add(new StringType(String.valueOf(c)).noExtensions()); 6052 } 6053 } 6054 return result; 6055 } 6056 6057 private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6058 List<Base> result = new ArrayList<Base>(); 6059 if (focus.size() == 0) { 6060 // no result, and don't need to do anything (including evaluate the parameter) 6061 return result; 6062 } 6063 6064 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 6065 String sw = convertToString(swb); 6066 if (swb.size() == 0) { 6067 // no result 6068 } else if (Utilities.noString(sw)) { 6069 result.add(new IntegerType(0).noExtensions()); 6070 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 6071 String s = convertToString(focus.get(0)); 6072 if (s == null) { 6073 result.add(new IntegerType(0).noExtensions()); 6074 } else { 6075 result.add(new IntegerType(s.indexOf(sw)).noExtensions()); 6076 } 6077 } 6078 return result; 6079 } 6080 6081 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6082 List<Base> result = new ArrayList<Base>(); 6083 if (focus.size() == 0) { 6084 // if there is no focus, then we don't need to do anything (including evaluate the parameter(s)) 6085 return result; 6086 } 6087 6088 List<Base> n1 = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 6089 if (n1.size() == 0) { 6090 // the start parameter is not present, so return an empty list) 6091 return result; 6092 } 6093 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 6094 int i2 = -1; 6095 if (exp.parameterCount() == 2) { 6096 List<Base> n2 = execute(context, baseToList(context.thisItem), exp.getParameters().get(1), true); 6097 if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) { 6098 return new ArrayList<Base>(); 6099 } 6100 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 6101 } 6102 6103 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 6104 String sw = convertToString(focus.get(0)); 6105 String s; 6106 if (i1 < 0 || i1 >= sw.length()) { 6107 return new ArrayList<Base>(); 6108 } 6109 if (exp.parameterCount() == 2) { 6110 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 6111 } else { 6112 s = sw.substring(i1); 6113 } 6114 if (!Utilities.noString(s)) { 6115 result.add(new StringType(s).noExtensions()); 6116 } 6117 } 6118 return result; 6119 } 6120 6121 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6122 String s = convertToString(focus); 6123 List<Base> result = new ArrayList<Base>(); 6124 if (Utilities.isInteger(s)) { 6125 result.add(new IntegerType(s).noExtensions()); 6126 } else if ("true".equals(s)) { 6127 result.add(new IntegerType(1).noExtensions()); 6128 } else if ("false".equals(s)) { 6129 result.add(new IntegerType(0).noExtensions()); 6130 } 6131 return result; 6132 } 6133 6134 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6135 List<Base> result = new ArrayList<Base>(); 6136 if (focus.size() != 1) { 6137 result.add(new BooleanType(false).noExtensions()); 6138 } else if (focus.get(0) instanceof IntegerType) { 6139 result.add(new BooleanType(true).noExtensions()); 6140 } else if (focus.get(0) instanceof BooleanType) { 6141 result.add(new BooleanType(true).noExtensions()); 6142 } else if (focus.get(0) instanceof StringType) { 6143 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 6144 } else { 6145 result.add(new BooleanType(false).noExtensions()); 6146 } 6147 return result; 6148 } 6149 6150 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6151 List<Base> result = new ArrayList<Base>(); 6152 if (focus.size() != 1) { 6153 result.add(new BooleanType(false).noExtensions()); 6154 } else if (focus.get(0) instanceof IntegerType) { 6155 result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); 6156 } else if (focus.get(0) instanceof DecimalType) { 6157 result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); 6158 } else if (focus.get(0) instanceof BooleanType) { 6159 result.add(new BooleanType(true).noExtensions()); 6160 } else if (focus.get(0) instanceof StringType) { 6161 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); 6162 } else { 6163 result.add(new BooleanType(false).noExtensions()); 6164 } 6165 return result; 6166 } 6167 6168 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6169 List<Base> result = new ArrayList<Base>(); 6170 if (focus.size() != 1) { 6171 result.add(new BooleanType(false).noExtensions()); 6172 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 6173 result.add(new BooleanType(true).noExtensions()); 6174 } else if (focus.get(0) instanceof StringType) { 6175 result.add(new BooleanType((convertToString(focus.get(0)).matches 6176 ("([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()); 6177 } else { 6178 result.add(new BooleanType(false).noExtensions()); 6179 } 6180 return result; 6181 } 6182 6183 private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6184 List<Base> result = new ArrayList<Base>(); 6185 if (focus.size() != 1) { 6186 result.add(new BooleanType(false).noExtensions()); 6187 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 6188 result.add(new BooleanType(true).noExtensions()); 6189 } else if (focus.get(0) instanceof StringType) { 6190 result.add(new BooleanType((convertToString(focus.get(0)).matches 6191 ("([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()); 6192 } else { 6193 result.add(new BooleanType(false).noExtensions()); 6194 } 6195 return result; 6196 } 6197 6198 private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { 6199 if (hostServices == null) { 6200 throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo"); 6201 } 6202 List<Base> result = new ArrayList<Base>(); 6203 if (focus.size() != 1) { 6204 result.add(new BooleanType(false).noExtensions()); 6205 } else { 6206 String url = convertToString(execute(context, baseToList(context.thisItem), expr.getParameters().get(0), true)); 6207 result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo, focus.get(0), url)).noExtensions()); 6208 } 6209 return result; 6210 } 6211 6212 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6213 List<Base> result = new ArrayList<Base>(); 6214 if (focus.size() != 1) { 6215 result.add(new BooleanType(false).noExtensions()); 6216 } else if (focus.get(0) instanceof TimeType) { 6217 result.add(new BooleanType(true).noExtensions()); 6218 } else if (focus.get(0) instanceof StringType) { 6219 result.add(new BooleanType((convertToString(focus.get(0)).matches 6220 ("(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()); 6221 } else { 6222 result.add(new BooleanType(false).noExtensions()); 6223 } 6224 return result; 6225 } 6226 6227 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6228 List<Base> result = new ArrayList<Base>(); 6229 if (focus.size() != 1) { 6230 result.add(new BooleanType(false).noExtensions()); 6231 } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) { 6232 result.add(new BooleanType(true).noExtensions()); 6233 } else { 6234 result.add(new BooleanType(false).noExtensions()); 6235 } 6236 return result; 6237 } 6238 6239 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6240 // also not checking the optional parameter for units 6241 List<Base> result = new ArrayList<Base>(); 6242 if (focus.size() != 1) { 6243 result.add(new BooleanType(false).noExtensions()); 6244 } else if (focus.get(0) instanceof IntegerType) { 6245 result.add(new BooleanType(true).noExtensions()); 6246 } else if (focus.get(0) instanceof DecimalType) { 6247 result.add(new BooleanType(true).noExtensions()); 6248 } else if (makeQuantity(focus.get(0)) instanceof Quantity) { 6249 result.add(new BooleanType(true).noExtensions()); 6250 } else if (focus.get(0) instanceof BooleanType) { 6251 result.add(new BooleanType(true).noExtensions()); 6252 } else if (focus.get(0) instanceof StringType) { 6253 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 6254 result.add(new BooleanType(q != null).noExtensions()); 6255 } else { 6256 result.add(new BooleanType(false).noExtensions()); 6257 } 6258 return result; 6259 } 6260 6261 public Quantity parseQuantityString(String s) { 6262 if (s == null) { 6263 return null; 6264 } 6265 s = s.trim(); 6266 if (s.contains(" ")) { 6267 String v = s.substring(0, s.indexOf(" ")).trim(); 6268 s = s.substring(s.indexOf(" ")).trim(); 6269 if (!Utilities.isDecimal(v, false)) { 6270 return null; 6271 } 6272 if (s.startsWith("'") && s.endsWith("'")) { 6273 return Quantity.fromUcum(v, s.substring(1, s.length()-1)); 6274 } 6275 if (s.equals("year") || s.equals("years")) { 6276 return Quantity.fromUcum(v, "a"); 6277 } else if (s.equals("month") || s.equals("months")) { 6278 return Quantity.fromUcum(v, "mo_s"); 6279 } else if (s.equals("week") || s.equals("weeks")) { 6280 return Quantity.fromUcum(v, "wk"); 6281 } else if (s.equals("day") || s.equals("days")) { 6282 return Quantity.fromUcum(v, "d"); 6283 } else if (s.equals("hour") || s.equals("hours")) { 6284 return Quantity.fromUcum(v, "h"); 6285 } else if (s.equals("minute") || s.equals("minutes")) { 6286 return Quantity.fromUcum(v, "min"); 6287 } else if (s.equals("second") || s.equals("seconds")) { 6288 return Quantity.fromUcum(v, "s"); 6289 } else if (s.equals("millisecond") || s.equals("milliseconds")) { 6290 return Quantity.fromUcum(v, "ms"); 6291 } else { 6292 return null; 6293 } 6294 } else { 6295 if (Utilities.isDecimal(s, true)) { 6296 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 6297 } else { 6298 return null; 6299 } 6300 } 6301 } 6302 6303 6304 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6305 List<Base> result = new ArrayList<Base>(); 6306 if (focus.size() != 1) { 6307 result.add(new BooleanType(false).noExtensions()); 6308 } else if (focus.get(0) instanceof IntegerType) { 6309 result.add(new BooleanType(true).noExtensions()); 6310 } else if (focus.get(0) instanceof BooleanType) { 6311 result.add(new BooleanType(true).noExtensions()); 6312 } else if (focus.get(0) instanceof DecimalType) { 6313 result.add(new BooleanType(true).noExtensions()); 6314 } else if (focus.get(0) instanceof StringType) { 6315 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); 6316 } else { 6317 result.add(new BooleanType(false).noExtensions()); 6318 } 6319 return result; 6320 } 6321 6322 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6323 List<Base> result = new ArrayList<Base>(); 6324 result.add(new IntegerType(focus.size()).noExtensions()); 6325 return result; 6326 } 6327 6328 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6329 List<Base> n1 = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 6330 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 6331 6332 List<Base> result = new ArrayList<Base>(); 6333 for (int i = i1; i < focus.size(); i++) { 6334 result.add(focus.get(i)); 6335 } 6336 return result; 6337 } 6338 6339 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6340 List<Base> result = new ArrayList<Base>(); 6341 for (int i = 1; i < focus.size(); i++) { 6342 result.add(focus.get(i)); 6343 } 6344 return result; 6345 } 6346 6347 6348 private class FHIRPathBaseSorter implements Comparator<Base> { 6349 6350 private ExecutionContext context; 6351 private ExpressionNode expr; 6352 6353 public FHIRPathBaseSorter(IWorkerContext worker, ExecutionContext context, ExpressionNode expr) { 6354 this.context = context; 6355 this.expr = expr; 6356 } 6357 6358 @Override 6359 public int compare(Base o1, Base o2) { 6360 if (expr.getParameters().size() == 0) { 6361 // natural sort order 6362 return compareNatural(o1, o2, null); 6363 } 6364 for (ExpressionNode p : expr.getParameters()) { 6365 boolean reversed = false; 6366 ExpressionNode pf = p; 6367 if (pf.getKind() == Kind.Unary) { 6368 reversed = true; 6369 pf = pf.getOpNext(); 6370 } 6371 Base b1 = executeExpression(o1, pf); 6372 Base b2 = executeExpression(o2, pf); 6373 int res = compareNatural(b1, b2, pf.toString()); 6374 if (res != 0) { 6375 return reversed ? -res : res; 6376 } 6377 } 6378 return 0; 6379 } 6380 6381 private Base executeExpression(Base b, ExpressionNode pf) { 6382 List<Base> focus = new ArrayList<Base>(); 6383 focus.add(b); 6384 focus = execute(changeThis(context, b), focus, pf, true); 6385 if (focus.size() == 0) { 6386 return null; 6387 } else if (focus.size() > 1) { 6388 throw new FHIRException(worker.formatMessage(I18nConstants.FHIRPATH_MUTIPLE_VALUES_IN_SORT, pf.toString())); 6389 } else { 6390 return focus.get(0); 6391 } 6392 } 6393 6394 private int compareNatural(Base o1, Base o2, String sorter) { 6395 if (o1 == null && o2 == null) { 6396 return 0; 6397 } 6398 if (o1 == null) { 6399 return 1; 6400 } 6401 if (o2 == null) { 6402 return -1; 6403 } 6404 6405 if (!isComparableTypes(o1.fhirType(), o2.fhirType())) { 6406 throw new FHIRException(worker.formatMessage(sorter == null ? I18nConstants.FHIRPATH_MUTIPLE_SORT_TYPE_PROBLEM_NATURAL : I18nConstants.FHIRPATH_MUTIPLE_SORT_TYPE_PROBLEM, o1.fhirType(), o2.fhirType(), sorter)); 6407 } 6408 String s1 = o1.primitiveValue(); 6409 String s2 = o2.primitiveValue(); 6410 if (Utilities.existsInList(o1.fhirType(), "integer", "unsignedInt", "positiveInt", "integer64")) { 6411 return Integer.compare(Integer.parseInt(s1),Integer.parseInt(s2)); 6412 } else if (Utilities.existsInList(o1.fhirType(), "decimal")) { 6413 return Double.compare(Double.parseDouble(s1),Double.parseDouble(s2)); 6414 } else if (Utilities.existsInList(o1.fhirType(), "boolean")) { 6415 return s2.compareTo(s1); // order is deliberately reverse 6416 } else { 6417 return s1.compareTo(s2); // order is deliberately reverse 6418 } 6419 } 6420 6421 private boolean isComparableTypes(String t1, String t2) { 6422 if (!Utilities.existsInList(t1, "integer", "unsignedInt", "positiveInt", "decimal", "boolean", "date", "dateTime", "time", "instant", "string", "code", "url", "uri", "uuid", "oid", "canonical", "integer64")) { 6423 return false; 6424 } 6425 if (!Utilities.existsInList(t2, "integer", "unsignedInt", "positiveInt", "decimal", "boolean", "date", "dateTime", "time", "instant", "string", "code", "url", "uri", "uuid", "oid", "canonical", "integer64")) { 6426 return false; 6427 } 6428 if (Utilities.existsInList(t1, "integer", "unsignedInt", "positiveInt", "integer64") && Utilities.existsInList(t2, "integer", "unsignedInt", "positiveInt", "integer64")) { 6429 return true; 6430 } 6431 if (Utilities.existsInList(t1, "boolean") && Utilities.existsInList(t2, "boolean")) { 6432 return true; 6433 } 6434 if (Utilities.existsInList(t1, "decimal") && Utilities.existsInList(t2, "decimal")) { 6435 return true; 6436 } 6437 if (Utilities.existsInList(t1, "time") && Utilities.existsInList(t2, "time")) { 6438 return true; 6439 } 6440 if (Utilities.existsInList(t1, "date", "dateTime", "instant") && Utilities.existsInList(t2, "date", "dateTime", "instant")) { 6441 return true; 6442 } 6443 if (Utilities.existsInList(t1, "string", "code", "url", "uri", "uuid", "oid", "canonical") && Utilities.existsInList(t2, "string", "code", "url", "uri", "uuid", "oid", "canonical")) { 6444 return true; 6445 } 6446 return false; 6447 } 6448 6449 } 6450 6451 private List<Base> funcSort(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 6452 List<Base> result = new ArrayList<Base>(); 6453 result.addAll(focus); 6454 Collections.sort(result, new FHIRPathBaseSorter(this.getWorker(), context, expr)); 6455 return result; 6456 } 6457 6458 6459 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6460 List<Base> result = new ArrayList<Base>(); 6461 if (focus.size() > 0) { 6462 result.add(focus.get(focus.size()-1)); 6463 } 6464 return result; 6465 } 6466 6467 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6468 List<Base> result = new ArrayList<Base>(); 6469 if (focus.size() > 0) { 6470 result.add(focus.get(0)); 6471 } 6472 return result; 6473 } 6474 6475 6476 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6477 List<Base> result = new ArrayList<Base>(); 6478 List<Base> pc = new ArrayList<Base>(); 6479 for (Base item : focus) { 6480 pc.clear(); 6481 pc.add(item); 6482 Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 6483 if (v == Equality.True) { 6484 result.add(item); 6485 } 6486 } 6487 return result; 6488 } 6489 6490 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6491 List<Base> result = new ArrayList<Base>(); 6492 List<Base> pc = new ArrayList<Base>(); 6493 int i = 0; 6494 for (Base item : focus) { 6495 pc.clear(); 6496 pc.add(item); 6497 result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true)); 6498 i++; 6499 } 6500 return result; 6501 } 6502 6503 private List<Base> funcCoalesce(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6504 // iterate over all the parameters, and return the first one that has a value 6505 for (ExpressionNode p : exp.getParameters()) { 6506 List<Base> pc = execute(context, focus, p, true); 6507 if (pc.size() > 0) { 6508 return pc; 6509 } 6510 } 6511 6512 // no result from any of the parameters, so return an empty list 6513 List<Base> result = new ArrayList<Base>(); 6514 return result; 6515 } 6516 6517 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6518 List<Base> result = new ArrayList<Base>(); 6519 String s = convertToString(execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)); 6520 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) { 6521 result.add(focus.get(Integer.parseInt(s))); 6522 } 6523 return result; 6524 } 6525 6526 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6527 List<Base> result = new ArrayList<Base>(); 6528 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 6529 return result; 6530 } 6531 6532 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 6533 List<Base> result = new ArrayList<Base>(); 6534 Equality v = asBool(focus, exp); 6535 if (v != Equality.Null) { 6536 result.add(new BooleanType(v != Equality.True)); 6537 } 6538 return result; 6539 } 6540 6541 private class ElementDefinitionMatch { 6542 private ElementDefinition definition; 6543 private ElementDefinition sourceDefinition; // if there was a content reference 6544 private String fixedType; 6545 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 6546 super(); 6547 this.definition = definition; 6548 this.fixedType = fixedType; 6549 } 6550 public ElementDefinition getDefinition() { 6551 return definition; 6552 } 6553 public ElementDefinition getSourceDefinition() { 6554 return sourceDefinition; 6555 } 6556 public String getFixedType() { 6557 return fixedType; 6558 } 6559 6560 } 6561 6562 private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException { 6563 if (Utilities.noString(type)) { 6564 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName"); 6565 } 6566 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) { 6567 return; 6568 } 6569 6570 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 6571 getSimpleTypeChildTypesByName(name, result); 6572 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 6573 getClassInfoChildTypesByName(name, result); 6574 } else { 6575 if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { 6576 return; 6577 } 6578 6579 String url = null; 6580 if (type.contains("#")) { 6581 url = type.substring(0, type.indexOf("#")); 6582 } else { 6583 url = type; 6584 } 6585 String tail = ""; 6586 StructureDefinition sd = worker.fetchTypeDefinition(url); 6587 if (sd == null) { 6588 sd = worker.fetchResource(StructureDefinition.class, url); 6589 } 6590 if (sd == null) { 6591 if (url.startsWith(TypeDetails.FP_NS)) { 6592 return; 6593 } else { 6594 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName#1"); 6595 } 6596 } 6597 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 6598 ElementDefinitionMatch m = null; 6599 if (type.contains("#")) { 6600 List<ElementDefinitionMatch> list = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr); 6601 m = list.size() == 1 ? list.get(0) : null; 6602 } 6603 if (m != null && hasDataType(m.definition)) { 6604 if (m.fixedType != null) { 6605 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd); 6606 if (dt == null) { 6607 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName#2"); 6608 } 6609 sdl.add(dt); 6610 } else 6611 for (TypeRefComponent t : m.definition.getType()) { 6612 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null)); 6613 if (dt == null) { 6614 throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName#3"); 6615 } 6616 addTypeAndDescendents(sdl, dt, cu.allStructures()); 6617 // also add any descendant types 6618 } 6619 } else { 6620 addTypeAndDescendents(sdl, sd, cu.allStructures()); 6621 if (type.contains("#")) { 6622 tail = type.substring(type.indexOf("#")+1); 6623 if (tail.contains(".")) { 6624 tail = tail.substring(tail.indexOf(".")); 6625 } else { 6626 tail = ""; 6627 } 6628 } 6629 } 6630 6631 for (StructureDefinition sdi : sdl) { 6632 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 6633 if (name.equals("**")) { 6634 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 6635 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6636 if (ed.getPath().startsWith(path)) { 6637 if (ed.hasContentReference()) { 6638 String cpath = ed.getContentReference(); 6639 if (!cpath.startsWith("#")) { 6640 if (!cpath.contains("#")) { 6641 throw new Error("ContentReference doesn't contain a #: "+cpath); 6642 } 6643 cpath = cpath.substring(cpath.indexOf("#")); 6644 } 6645 String tn = sdi.getType()+cpath; 6646 if (!result.hasType(worker, tn)) { 6647 if (elementDependencies != null) { 6648 elementDependencies.add(ed); 6649 } 6650 getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); 6651 } 6652 } else { 6653 for (TypeRefComponent t : ed.getType()) { 6654 if (t.hasCode() && t.getCodeElement().hasValue()) { 6655 String tn = null; 6656 if (Utilities.existsInList(t.getCode(), "Element", "BackboneElement", "Base") || cu.isAbstractType(t.getCode())) { 6657 tn = sdi.getType()+"#"+ed.getPath(); 6658 } else { 6659 tn = t.getCode(); 6660 } 6661 if (t.getCode().equals("Resource")) { 6662 for (String rn : worker.getResourceNames()) { 6663 if (!result.hasType(worker, rn)) { 6664 if (elementDependencies != null) { 6665 elementDependencies.add(ed); 6666 } 6667 getChildTypesByName(result.addType(rn), "**", result, expr, null, elementDependencies); 6668 } 6669 } 6670 } else if (!result.hasType(worker, tn)) { 6671 if (elementDependencies != null) { 6672 elementDependencies.add(ed); 6673 } 6674 getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies); 6675 } 6676 } 6677 } 6678 } 6679 } 6680 } 6681 } else if (name.equals("*")) { 6682 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 6683 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6684 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 6685 for (TypeRefComponent t : ed.getType()) { 6686 if (Utilities.noString(t.getCode())) { // Element.id or Extension.url 6687 if (elementDependencies != null) { 6688 elementDependencies.add(ed); 6689 } 6690 result.addType("System.string"); 6691 } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { 6692 if (elementDependencies != null) { 6693 elementDependencies.add(ed); 6694 } 6695 result.addType(sdi.getType()+"#"+ed.getPath()); 6696 } else if (t.getCode().equals("Resource")) { 6697 if (elementDependencies != null) { 6698 elementDependencies.add(ed); 6699 } 6700 result.addTypes(worker.getResourceNames()); 6701 } else { 6702 if (elementDependencies != null) { 6703 elementDependencies.add(ed); 6704 } 6705 result.addType(t.getCode()); 6706 copyTargetProfiles(ed, t, focus, result); 6707 } 6708 } 6709 } 6710 } else { 6711 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 6712 6713 List<ElementDefinitionMatch> edl = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); 6714 for (ElementDefinitionMatch ed : edl) { 6715 if (ed.getDefinition().isChoice()) { 6716 result.setChoice(true); 6717 } 6718 if (!Utilities.noString(ed.getFixedType())) { 6719 if (elementDependencies != null) { 6720 elementDependencies.add(ed.definition); 6721 } 6722 result.addType(ed.getFixedType()); 6723 } else if (ed.getSourceDefinition() != null) { 6724 ProfiledType pt = new ProfiledType(sdi.getType()+"#"+ed.definition.getPath()); 6725 result.addType(ed.getSourceDefinition().unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); 6726 } else { 6727 for (TypeRefComponent t : ed.getDefinition().getType()) { 6728 if (Utilities.noString(t.getCode())) { 6729 if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { 6730 if (elementDependencies != null) { 6731 elementDependencies.add(ed.definition); 6732 } 6733 result.addType(TypeDetails.FP_NS, "System.String"); 6734 } 6735 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 6736 } 6737 6738 ProfiledType pt = null; 6739 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement") || isAbstractType(t.getCode())) { 6740 pt = new ProfiledType(sdi.getUrl()+"#"+path); 6741 } else if (t.getCode().equals("Resource")) { 6742 if (elementDependencies != null) { 6743 elementDependencies.add(ed.definition); 6744 } 6745 result.addTypes(worker.getResourceNames()); 6746 } else { 6747 pt = new ProfiledType(t.getWorkingCode()); 6748 } 6749 if (pt != null) { 6750 if (t.hasProfile()) { 6751 pt.addProfiles(t.getProfile()); 6752 } 6753 if (ed.getDefinition().hasBinding()) { 6754 pt.addBinding(ed.getDefinition().getBinding()); 6755 } 6756 if (elementDependencies != null) { 6757 elementDependencies.add(ed.definition); 6758 } 6759 result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt); 6760 copyTargetProfiles(ed.getDefinition(), t, focus, result); 6761 } 6762 } 6763 } 6764 } 6765 } 6766 } 6767 } 6768 } 6769 6770 private void copyTargetProfiles(ElementDefinition ed, TypeRefComponent t, TypeDetails focus, TypeDetails result) { 6771 if (t.hasTargetProfile()) { 6772 for (CanonicalType u : t.getTargetProfile()) { 6773 result.addTarget(u.primitiveValue()); 6774 } 6775 } else if (focus != null && focus.hasType("CodeableReference") && ed.getPath().endsWith(".reference") && focus.getTargets() != null) { // special case, targets are on parent 6776 for (String s : focus.getTargets()) { 6777 result.addTarget(s); 6778 } 6779 } 6780 } 6781 6782 private void addTypeAndDescendents(List<StructureDefinition> sdl, StructureDefinition dt, List<StructureDefinition> types) { 6783 sdl.add(dt); 6784 for (StructureDefinition sd : types) { 6785 if (sd.hasBaseDefinition() && sd.getBaseDefinitionNoVersion().equals(dt.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 6786 addTypeAndDescendents(sdl, sd, types); 6787 } 6788 } 6789 } 6790 6791 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 6792 if (name.equals("namespace")) { 6793 result.addType(TypeDetails.FP_String); 6794 } 6795 if (name.equals("name")) { 6796 result.addType(TypeDetails.FP_String); 6797 } 6798 } 6799 6800 6801 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 6802 if (name.equals("namespace")) { 6803 result.addType(TypeDetails.FP_String); 6804 } 6805 if (name.equals("name")) { 6806 result.addType(TypeDetails.FP_String); 6807 } 6808 } 6809 6810 6811 public List<ElementDefinitionMatch> getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException { 6812 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6813 if (ed.getPath().equals(path)) { 6814 if (ed.hasContentReference()) { 6815 ElementDefinitionMatch res = getElementDefinitionById(sd, ed.getContentReference()); 6816 if (res == null) { 6817 throw new Error("Unable to find "+ed.getContentReference()); 6818 } else { 6819 res.sourceDefinition = ed; 6820 } 6821 return ml(res); 6822 } else { 6823 return ml(new ElementDefinitionMatch(ed, null)); 6824 } 6825 } 6826 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) { 6827 return ml(new ElementDefinitionMatch(ed, null)); 6828 } 6829 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 6830 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 6831 if (primitiveTypes.contains(s)) { 6832 return ml(new ElementDefinitionMatch(ed, s)); 6833 } else { 6834 return ml(new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3))); 6835 } 6836 } 6837 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 6838 // now we walk into the type. 6839 if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this, but we can get here with CDA 6840 List<ElementDefinitionMatch> list = new ArrayList<>(); 6841 // for each type, does it have the next node in the path? 6842 for (TypeRefComponent tr : ed.getType()) { 6843 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tr.getCode(), null), sd); 6844 if (nsd == null) { 6845 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); 6846 } 6847 List<ElementDefinitionMatch> edl = getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); 6848 list.addAll(edl); 6849 } 6850 return list; 6851 } 6852 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null), sd); 6853 if (nsd == null) { 6854 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition"); 6855 } 6856 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr); 6857 } 6858 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 6859 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 6860 if (m == null) { 6861 throw new Error("Unable to find path "+path+" with a content reference of "+ed.getContentReference()); 6862 } else { 6863 List<ElementDefinitionMatch> res = getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr); 6864 for (ElementDefinitionMatch item : res) { 6865 item.sourceDefinition = ed; 6866 } 6867 return res; 6868 } 6869 } 6870 } 6871 return ml(null); 6872 } 6873 6874 private List<ElementDefinitionMatch> ml(ElementDefinitionMatch item) { 6875 List<ElementDefinitionMatch> list = new ArrayList<>(); 6876 if (item != null) { 6877 list.add(item); 6878 } 6879 return list; 6880 } 6881 6882 private boolean isAbstractType(List<TypeRefComponent> list) { 6883 if (list.size() != 1) { 6884 return false; 6885 } else { 6886 return isAbstractType(list.get(0).getCode()); 6887 } 6888 } 6889 6890 private boolean isAbstractType(String code) { 6891 StructureDefinition sd = worker.fetchTypeDefinition(code); 6892 return sd != null && sd.getAbstract() && sd.getKind() != StructureDefinitionKind.RESOURCE; 6893 } 6894 6895 6896 private boolean hasType(ElementDefinition ed, String s) { 6897 for (TypeRefComponent t : ed.getType()) { 6898 if (s.equalsIgnoreCase(t.getCode())) { 6899 return true; 6900 } 6901 } 6902 return false; 6903 } 6904 6905 private boolean hasDataType(ElementDefinition ed) { 6906 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement") || isAbstractType(ed.getType().get(0).getCode())); 6907 } 6908 6909 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 6910 StructureDefinition sdt = sd; 6911 while (sdt != null) { 6912 if (ref.startsWith(sdt.getUrl()+"#")) { 6913 ref = ref.replace(sdt.getUrl()+"#", "#"); 6914 break; 6915 } 6916 sdt = worker.fetchResource(StructureDefinition.class, sdt.getBaseDefinition()); 6917 } 6918 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6919 if (ref.equals("#"+ed.getId())) { 6920 return new ElementDefinitionMatch(ed, null); 6921 } 6922 } 6923 return null; 6924 } 6925 6926 6927 public boolean hasLog() { 6928 return traceLog != null && traceLog.length() > 0; 6929 } 6930 6931 6932 public String takeLog() { 6933 if (!hasLog()) { 6934 return ""; 6935 } 6936 String s = traceLog.toString(); 6937 traceLog = new StringBuilder(); 6938 return s; 6939 } 6940 6941 6942 /** given an element definition in a profile, what element contains the differentiating fixed 6943 * for the element, given the differentiating expression. The expression is only allowed to 6944 * use a subset of FHIRPath 6945 * 6946 * @param profile 6947 * @param element 6948 * @return 6949 * @throws PathEngineException 6950 * @throws DefinitionException 6951 */ 6952 public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException { 6953 StructureDefinition sd = profile; 6954 TypedElementDefinition focus = null; 6955 boolean okToNotResolve = false; 6956 6957 if (expr.getKind() == Kind.Name) { 6958 if (element.getElement().hasSlicing()) { 6959 ElementDefinition slice = pickMandatorySlice(sd, element.getElement()); 6960 if (slice == null) { 6961 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId()); 6962 } 6963 element = new TypedElementDefinition(slice); 6964 } 6965 6966 if (expr.getName().equals("$this")) { 6967 focus = element; 6968 } else { 6969 SourcedChildDefinitions childDefinitions; 6970 childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), false); 6971 // if that's empty, get the children of the type 6972 if (childDefinitions.getList().isEmpty()) { 6973 6974 sd = fetchStructureByType(element, expr); 6975 if (sd == null) { 6976 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId()); 6977 } 6978 childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep(), false); 6979 } 6980 for (ElementDefinition t : childDefinitions.getList()) { 6981 if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) 6982 focus = new TypedElementDefinition(t); 6983 break; 6984 } 6985 } 6986 } 6987 } else if (expr.getKind() == Kind.Function) { 6988 if ("resolve".equals(expr.getName())) { 6989 if (element.getTypes().size() == 0) { 6990 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId()); 6991 } 6992 if (element.getTypes().size() > 1) { 6993 throw makeExceptionPlural(element.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId()); 6994 } 6995 if (!element.getTypes().get(0).hasTarget()) { 6996 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")"); 6997 } 6998 if (element.getTypes().get(0).getTargetProfile().size() > 1) { 6999 throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId()); 7000 } 7001 sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue(), profile); 7002 if (sd == null) { 7003 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId()); 7004 } 7005 focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep()); 7006 } else if ("extension".equals(expr.getName())) { 7007 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 7008 SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), true); 7009 for (ElementDefinition t : childDefinitions.getList()) { 7010 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 7011 StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? 7012 null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue(), profile); 7013 while (exsd != null && !exsd.getBaseDefinitionNoVersion().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { 7014 exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd); 7015 } 7016 if (exsd != null && exsd.getUrl().equals(targetUrl)) { 7017 if (profileUtilities.getChildMap(sd, t, false).getList().isEmpty()) { 7018 sd = exsd; 7019 } 7020 focus = new TypedElementDefinition(t); 7021 break; 7022 } 7023 } 7024 } 7025 if (focus == null) { 7026 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl()); 7027 } 7028 } else if ("ofType".equals(expr.getName())) { 7029 if (!element.getElement().hasType()) { 7030 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId()); 7031 } 7032 List<String> atn = new ArrayList<>(); 7033 for (TypeRefComponent tr : element.getTypes()) { 7034 if (!tr.hasCode()) { 7035 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId()); 7036 } 7037 atn.add(tr.getCode()); 7038 } 7039 String stn = expr.getParameters().get(0).getName(); 7040 okToNotResolve = true; 7041 if ((atn.contains(stn))) { 7042 if (element.getTypes().size() > 1) { 7043 focus = new TypedElementDefinition( element.getSrc(), element.getElement(), stn); 7044 } else { 7045 focus = element; 7046 } 7047 } 7048 } else { 7049 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName()); 7050 } 7051 } else if (expr.getKind() == Kind.Group) { 7052 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString()); 7053 } else if (expr.getKind() == Kind.Constant) { 7054 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); 7055 } 7056 7057 if (focus == null) { 7058 if (okToNotResolve) { 7059 return null; 7060 } else { 7061 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl()); 7062 } 7063 } else { 7064 // gdg 26-02-2022. If we're walking towards a resolve() and we're on a reference, and we try to walk into the reference 7065 // then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined 7066 // on the Reference, not the reference.reference. 7067 ExpressionNode next = expr.getInner(); 7068 if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) { 7069 next = next.getInner(); 7070 } 7071 if (next == null) { 7072 return focus; 7073 } else { 7074 return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences); 7075 } 7076 } 7077 } 7078 7079 private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException { 7080 List<ElementDefinition> list = profileUtilities.getSliceList(sd, element); 7081 for (ElementDefinition ed : list) { 7082 if (ed.getMin() > 0) { 7083 return ed; 7084 } 7085 } 7086 return null; 7087 } 7088 7089 7090 private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException { 7091 if (ed.getTypes().size() == 0) { 7092 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId()); 7093 } 7094 if (ed.getTypes().size() > 1) { 7095 throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId()); 7096 } 7097 if (ed.getTypes().get(0).getProfile().size() > 1) { 7098 throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId()); 7099 } 7100 if (ed.getTypes().get(0).hasProfile()) { 7101 return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue(), ed.getSrc()); 7102 } else { 7103 return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null), ed.getSrc()); 7104 } 7105 } 7106 7107 7108 private boolean tailMatches(ElementDefinition t, String d) { 7109 String tail = tailDot(t.getPath()); 7110 if (d.contains("[")) { 7111 return tail.startsWith(d.substring(0, d.indexOf('['))); 7112 } else if (tail.equals(d)) { 7113 return true; 7114 } 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())) { 7115 return tail.startsWith(d); 7116 } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) { 7117 return true; 7118 } 7119 return false; 7120 } 7121 7122 private String tailDot(String path) { 7123 return path.substring(path.lastIndexOf(".") + 1); 7124 } 7125 7126 private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException { 7127 if (items.size() == 0) { 7128 return Equality.Null; 7129 } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { 7130 return asBool(items.get(0), true); 7131 } else if (items.size() == 1) { 7132 return Equality.True; 7133 } else { 7134 throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items)); 7135 } 7136 } 7137 7138 private Equality asBoolFromInt(String s) { 7139 try { 7140 int i = Integer.parseInt(s); 7141 switch (i) { 7142 case 0: return Equality.False; 7143 case 1: return Equality.True; 7144 default: return Equality.Null; 7145 } 7146 } catch (Exception e) { 7147 return Equality.Null; 7148 } 7149 } 7150 7151 private Equality asBoolFromDec(String s) { 7152 try { 7153 BigDecimal d = new BigDecimal(s); 7154 if (d.compareTo(BigDecimal.ZERO) == 0) { 7155 return Equality.False; 7156 } else if (d.compareTo(BigDecimal.ONE) == 0) { 7157 return Equality.True; 7158 } else { 7159 return Equality.Null; 7160 } 7161 } catch (Exception e) { 7162 return Equality.Null; 7163 } 7164 } 7165 7166 private Equality asBool(Base item, boolean narrow) { 7167 if (item instanceof BooleanType) { 7168 return boolToTriState(((BooleanType) item).booleanValue()); 7169 } else if (item.isBooleanPrimitive()) { 7170 if (Utilities.existsInList(item.primitiveValue(), "true")) { 7171 return Equality.True; 7172 } else if (Utilities.existsInList(item.primitiveValue(), "false")) { 7173 return Equality.False; 7174 } else { 7175 return Equality.Null; 7176 } 7177 } else if (narrow) { 7178 return Equality.False; 7179 } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) { 7180 return asBoolFromInt(item.primitiveValue()); 7181 } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) { 7182 return asBoolFromDec(item.primitiveValue()); 7183 } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { 7184 if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) { 7185 return Equality.True; 7186 } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) { 7187 return Equality.False; 7188 } else if (Utilities.isInteger(item.primitiveValue())) { 7189 return asBoolFromInt(item.primitiveValue()); 7190 } else if (Utilities.isDecimal(item.primitiveValue(), true)) { 7191 return asBoolFromDec(item.primitiveValue()); 7192 } else { 7193 return Equality.Null; 7194 } 7195 } 7196 return Equality.Null; 7197 } 7198 7199 private Equality boolToTriState(boolean b) { 7200 return b ? Equality.True : Equality.False; 7201 } 7202 7203 7204 public ValidationOptions getTerminologyServiceOptions() { 7205 return terminologyServiceOptions; 7206 } 7207 7208 7209 public IWorkerContext getWorker() { 7210 return worker; 7211 } 7212 7213 public boolean isAllowPolymorphicNames() { 7214 return allowPolymorphicNames; 7215 } 7216 7217 public void setAllowPolymorphicNames(boolean allowPolymorphicNames) { 7218 this.allowPolymorphicNames = allowPolymorphicNames; 7219 } 7220 7221 public boolean isLiquidMode() { 7222 return liquidMode; 7223 } 7224 7225 public void setLiquidMode(boolean liquidMode) { 7226 this.liquidMode = liquidMode; 7227 } 7228 7229 public ProfileUtilities getProfileUtilities() { 7230 return profileUtilities; 7231 } 7232 7233 public boolean isAllowDoubleQuotes() { 7234 return allowDoubleQuotes; 7235 } 7236 public void setAllowDoubleQuotes(boolean allowDoubleQuotes) { 7237 this.allowDoubleQuotes = allowDoubleQuotes; 7238 } 7239 7240 public boolean isEmitSQLonFHIRWarning() { 7241 return emitSQLonFHIRWarning; 7242 } 7243 7244 public void setEmitSQLonFHIRWarning(boolean emitSQLonFHIRWarning) { 7245 this.emitSQLonFHIRWarning = emitSQLonFHIRWarning; 7246 } 7247 7248}