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