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