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