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