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); 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("decimal") 3667 && (focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) { 3668 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime); 3669 } else if (focus.hasType("decimal")) { 3670 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 3671 } else { 3672 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 3673 } 3674 } 3675 case Precision: { 3676 checkContextContinuous(focus, exp.getFunction().toCode(), exp); 3677 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 3678 } 3679 3680 case Custom: { 3681 return hostServices.checkFunction(this, context.appInfo, exp.getName(), focus, paramTypes); 3682 } 3683 default: 3684 break; 3685 } 3686 throw new Error("not Implemented yet"); 3687 } 3688 3689 private boolean isExpressionParameter(ExpressionNode exp, int i) { 3690 switch (i) { 3691 case 0: 3692 return exp.getFunction() == Function.Where || exp.getFunction() == Function.Exists 3693 || exp.getFunction() == Function.All || exp.getFunction() == Function.Select 3694 || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate; 3695 case 1: 3696 return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable; 3697 default: 3698 return false; 3699 } 3700 } 3701 3702 private void checkParamTypes(ExpressionNode expr, String funcName, List<TypeDetails> paramTypes, 3703 TypeDetails... typeSet) throws PathEngineException { 3704 int i = 0; 3705 for (TypeDetails pt : typeSet) { 3706 if (i == paramTypes.size()) { 3707 return; 3708 } 3709 TypeDetails actual = paramTypes.get(i); 3710 i++; 3711 for (String a : actual.getTypes()) { 3712 if (!pt.hasType(worker, a)) { 3713 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString()); 3714 } 3715 } 3716 } 3717 } 3718 3719 private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3720 if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) { 3721// typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT)); 3722 } 3723 } 3724 3725 private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3726 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) { 3727 throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name); 3728 } 3729 } 3730 3731 private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3732 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") 3733 && !focus.hasType(worker, "canonical")) { 3734 throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe()); 3735 } 3736 } 3737 3738 private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3739 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") 3740 && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) { 3741 throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe()); 3742 } 3743 } 3744 3745 private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) 3746 throws PathEngineException { 3747 if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") 3748 && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) { 3749 throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, 3750 name, focus.describe()); 3751 } 3752 } 3753 3754 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) 3755 throws PathEngineException { 3756 if (!focus.hasNoTypes()) { 3757 if (canQty) { 3758 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) { 3759 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), 3760 "Quantity, " + primitiveTypes.toString()); 3761 } 3762 } else if (!focus.hasType(primitiveTypes)) { 3763 throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), 3764 primitiveTypes.toString()); 3765 } 3766 } 3767 } 3768 3769 private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3770 if (!focus.hasNoTypes() && !focus.hasType("integer") && !focus.hasType("decimal") && !focus.hasType("Quantity")) { 3771 throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe()); 3772 } 3773 } 3774 3775 private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3776 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) { 3777 throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe()); 3778 } 3779 } 3780 3781 private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException { 3782 if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") 3783 && !focus.hasType("time") && !focus.hasType("Quantity")) { 3784 throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe()); 3785 } 3786 } 3787 3788 private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) 3789 throws PathEngineException, DefinitionException { 3790 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 3791 for (String f : focus.getTypes()) { 3792 getChildTypesByName(f, mask, result, expr); 3793 } 3794 return result; 3795 } 3796 3797 private TypeDetails anything(CollectionStatus status) { 3798 return new TypeDetails(status, allTypes.keySet()); 3799 } 3800 3801 // private boolean isPrimitiveType(String s) { 3802 // return s.equals("boolean") || s.equals("integer") || s.equals("decimal") || 3803 // s.equals("base64Binary") || s.equals("instant") || s.equals("string") || 3804 // s.equals("uri") || s.equals("date") || s.equals("dateTime") || 3805 // s.equals("time") || s.equals("code") || s.equals("oid") || s.equals("id") || 3806 // s.equals("unsignedInt") || s.equals("positiveInt") || s.equals("markdown"); 3807 // } 3808 3809 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) 3810 throws FHIRException { 3811 switch (exp.getFunction()) { 3812 case Empty: 3813 return funcEmpty(context, focus, exp); 3814 case Not: 3815 return funcNot(context, focus, exp); 3816 case Exists: 3817 return funcExists(context, focus, exp); 3818 case SubsetOf: 3819 return funcSubsetOf(context, focus, exp); 3820 case SupersetOf: 3821 return funcSupersetOf(context, focus, exp); 3822 case IsDistinct: 3823 return funcIsDistinct(context, focus, exp); 3824 case Distinct: 3825 return funcDistinct(context, focus, exp); 3826 case Count: 3827 return funcCount(context, focus, exp); 3828 case Where: 3829 return funcWhere(context, focus, exp); 3830 case Select: 3831 return funcSelect(context, focus, exp); 3832 case All: 3833 return funcAll(context, focus, exp); 3834 case Repeat: 3835 return funcRepeat(context, focus, exp); 3836 case Aggregate: 3837 return funcAggregate(context, focus, exp); 3838 case Item: 3839 return funcItem(context, focus, exp); 3840 case As: 3841 return funcAs(context, focus, exp); 3842 case OfType: 3843 return funcOfType(context, focus, exp); 3844 case Type: 3845 return funcType(context, focus, exp); 3846 case Is: 3847 return funcIs(context, focus, exp); 3848 case Single: 3849 return funcSingle(context, focus, exp); 3850 case First: 3851 return funcFirst(context, focus, exp); 3852 case Last: 3853 return funcLast(context, focus, exp); 3854 case Tail: 3855 return funcTail(context, focus, exp); 3856 case Skip: 3857 return funcSkip(context, focus, exp); 3858 case Take: 3859 return funcTake(context, focus, exp); 3860 case Union: 3861 return funcUnion(context, focus, exp); 3862 case Combine: 3863 return funcCombine(context, focus, exp); 3864 case Intersect: 3865 return funcIntersect(context, focus, exp); 3866 case Exclude: 3867 return funcExclude(context, focus, exp); 3868 case Iif: 3869 return funcIif(context, focus, exp); 3870 case Lower: 3871 return funcLower(context, focus, exp); 3872 case Upper: 3873 return funcUpper(context, focus, exp); 3874 case ToChars: 3875 return funcToChars(context, focus, exp); 3876 case IndexOf: 3877 return funcIndexOf(context, focus, exp); 3878 case Substring: 3879 return funcSubstring(context, focus, exp); 3880 case StartsWith: 3881 return funcStartsWith(context, focus, exp); 3882 case EndsWith: 3883 return funcEndsWith(context, focus, exp); 3884 case Matches: 3885 return funcMatches(context, focus, exp); 3886 case MatchesFull: 3887 return funcMatchesFull(context, focus, exp); 3888 case ReplaceMatches: 3889 return funcReplaceMatches(context, focus, exp); 3890 case Contains: 3891 return funcContains(context, focus, exp); 3892 case Replace: 3893 return funcReplace(context, focus, exp); 3894 case Length: 3895 return funcLength(context, focus, exp); 3896 case Children: 3897 return funcChildren(context, focus, exp); 3898 case Descendants: 3899 return funcDescendants(context, focus, exp); 3900 case MemberOf: 3901 return funcMemberOf(context, focus, exp); 3902 case Trace: 3903 return funcTrace(context, focus, exp); 3904 case DefineVariable: 3905 return funcDefineVariable(context, focus, exp); 3906 case Check: 3907 return funcCheck(context, focus, exp); 3908 case Today: 3909 return funcToday(context, focus, exp); 3910 case Now: 3911 return funcNow(context, focus, exp); 3912 case Resolve: 3913 return funcResolve(context, focus, exp); 3914 case Extension: 3915 return funcExtension(context, focus, exp); 3916 case AnyFalse: 3917 return funcAnyFalse(context, focus, exp); 3918 case AllFalse: 3919 return funcAllFalse(context, focus, exp); 3920 case AnyTrue: 3921 return funcAnyTrue(context, focus, exp); 3922 case AllTrue: 3923 return funcAllTrue(context, focus, exp); 3924 case HasValue: 3925 return funcHasValue(context, focus, exp); 3926 case Encode: 3927 return funcEncode(context, focus, exp); 3928 case Decode: 3929 return funcDecode(context, focus, exp); 3930 case Escape: 3931 return funcEscape(context, focus, exp); 3932 case Unescape: 3933 return funcUnescape(context, focus, exp); 3934 case Trim: 3935 return funcTrim(context, focus, exp); 3936 case Split: 3937 return funcSplit(context, focus, exp); 3938 case Join: 3939 return funcJoin(context, focus, exp); 3940 case HtmlChecks1: 3941 return funcHtmlChecks1(context, focus, exp); 3942 case HtmlChecks2: 3943 return funcHtmlChecks2(context, focus, exp); 3944 case Comparable: 3945 return funcComparable(context, focus, exp); 3946 case ToInteger: 3947 return funcToInteger(context, focus, exp); 3948 case ToDecimal: 3949 return funcToDecimal(context, focus, exp); 3950 case ToString: 3951 return funcToString(context, focus, exp); 3952 case ToBoolean: 3953 return funcToBoolean(context, focus, exp); 3954 case ToQuantity: 3955 return funcToQuantity(context, focus, exp); 3956 case ToDateTime: 3957 return funcToDateTime(context, focus, exp); 3958 case ToTime: 3959 return funcToTime(context, focus, exp); 3960 case ConvertsToInteger: 3961 return funcIsInteger(context, focus, exp); 3962 case ConvertsToDecimal: 3963 return funcIsDecimal(context, focus, exp); 3964 case ConvertsToString: 3965 return funcIsString(context, focus, exp); 3966 case ConvertsToBoolean: 3967 return funcIsBoolean(context, focus, exp); 3968 case ConvertsToQuantity: 3969 return funcIsQuantity(context, focus, exp); 3970 case ConvertsToDateTime: 3971 return funcIsDateTime(context, focus, exp); 3972 case ConvertsToDate: 3973 return funcIsDate(context, focus, exp); 3974 case ConvertsToTime: 3975 return funcIsTime(context, focus, exp); 3976 case ConformsTo: 3977 return funcConformsTo(context, focus, exp); 3978 case Round: 3979 return funcRound(context, focus, exp); 3980 case Sqrt: 3981 return funcSqrt(context, focus, exp); 3982 case Abs: 3983 return funcAbs(context, focus, exp); 3984 case Ceiling: 3985 return funcCeiling(context, focus, exp); 3986 case Exp: 3987 return funcExp(context, focus, exp); 3988 case Floor: 3989 return funcFloor(context, focus, exp); 3990 case Ln: 3991 return funcLn(context, focus, exp); 3992 case Log: 3993 return funcLog(context, focus, exp); 3994 case Power: 3995 return funcPower(context, focus, exp); 3996 case Truncate: 3997 return funcTruncate(context, focus, exp); 3998 case LowBoundary: 3999 return funcLowBoundary(context, focus, exp); 4000 case HighBoundary: 4001 return funcHighBoundary(context, focus, exp); 4002 case Precision: 4003 return funcPrecision(context, focus, exp); 4004 4005 case Custom: { 4006 List<List<Base>> params = new ArrayList<List<Base>>(); 4007 for (ExpressionNode p : exp.getParameters()) { 4008 params.add(execute(context, focus, p, true)); 4009 } 4010 return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params); 4011 } 4012 default: 4013 throw new Error("not Implemented yet"); 4014 } 4015 } 4016 4017 private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4018 if (focus.size() != 1) { 4019 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); 4020 } 4021 Base base = focus.get(0); 4022 List<Base> result = new ArrayList<Base>(); 4023 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4024 Double d = Double.parseDouble(base.primitiveValue()); 4025 try { 4026 result.add(new DecimalType(Math.sqrt(d))); 4027 } catch (Exception e) { 4028 // just return nothing 4029 } 4030 } else { 4031 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), 4032 "integer or decimal"); 4033 } 4034 return result; 4035 } 4036 4037 private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4038 if (focus.size() != 1) { 4039 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size()); 4040 } 4041 Base base = focus.get(0); 4042 List<Base> result = new ArrayList<Base>(); 4043 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4044 Double d = Double.parseDouble(base.primitiveValue()); 4045 try { 4046 result.add(new DecimalType(Math.abs(d))); 4047 } catch (Exception e) { 4048 // just return nothing 4049 } 4050 } else if (base.hasType("Quantity")) { 4051 Quantity qty = (Quantity) base; 4052 result.add(qty.copy().setValue(qty.getValue().abs())); 4053 } else { 4054 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), 4055 "integer or decimal"); 4056 } 4057 return result; 4058 } 4059 4060 private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4061 if (focus.size() != 1) { 4062 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size()); 4063 } 4064 Base base = focus.get(0); 4065 List<Base> result = new ArrayList<Base>(); 4066 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4067 Double d = Double.parseDouble(base.primitiveValue()); 4068 try { 4069 result.add(new IntegerType((int) Math.ceil(d))); 4070 } catch (Exception e) { 4071 // just return nothing 4072 } 4073 } else { 4074 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), 4075 "integer or decimal"); 4076 } 4077 return result; 4078 } 4079 4080 private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4081 if (focus.size() != 1) { 4082 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size()); 4083 } 4084 Base base = focus.get(0); 4085 List<Base> result = new ArrayList<Base>(); 4086 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4087 Double d = Double.parseDouble(base.primitiveValue()); 4088 try { 4089 result.add(new IntegerType((int) Math.floor(d))); 4090 } catch (Exception e) { 4091 // just return nothing 4092 } 4093 } else { 4094 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), 4095 "integer or decimal"); 4096 } 4097 return result; 4098 } 4099 4100 private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4101 if (focus.size() == 0) { 4102 return new ArrayList<Base>(); 4103 } 4104 if (focus.size() > 1) { 4105 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size()); 4106 } 4107 Base base = focus.get(0); 4108 List<Base> result = new ArrayList<Base>(); 4109 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4110 Double d = Double.parseDouble(base.primitiveValue()); 4111 try { 4112 result.add(new DecimalType(Math.exp(d))); 4113 } catch (Exception e) { 4114 // just return nothing 4115 } 4116 4117 } else { 4118 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), 4119 "integer or decimal"); 4120 } 4121 return result; 4122 } 4123 4124 private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4125 if (focus.size() != 1) { 4126 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size()); 4127 } 4128 Base base = focus.get(0); 4129 List<Base> result = new ArrayList<Base>(); 4130 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4131 Double d = Double.parseDouble(base.primitiveValue()); 4132 try { 4133 result.add(new DecimalType(Math.log(d))); 4134 } catch (Exception e) { 4135 // just return nothing 4136 } 4137 } else { 4138 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), 4139 "integer or decimal"); 4140 } 4141 return result; 4142 } 4143 4144 private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4145 if (focus.size() != 1) { 4146 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size()); 4147 } 4148 Base base = focus.get(0); 4149 List<Base> result = new ArrayList<Base>(); 4150 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4151 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4152 if (n1.size() != 1) { 4153 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", 4154 "integer or decimal"); 4155 } 4156 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4157 Double d = Double.parseDouble(base.primitiveValue()); 4158 try { 4159 result.add(new DecimalType(customLog(e, d))); 4160 } catch (Exception ex) { 4161 // just return nothing 4162 } 4163 } else { 4164 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), 4165 "integer or decimal"); 4166 } 4167 return result; 4168 } 4169 4170 private static double customLog(double base, double logNumber) { 4171 return Math.log(logNumber) / Math.log(base); 4172 } 4173 4174 private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4175 if (focus.size() != 1) { 4176 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size()); 4177 } 4178 Base base = focus.get(0); 4179 List<Base> result = new ArrayList<Base>(); 4180 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4181 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4182 if (n1.size() != 1) { 4183 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", 4184 "integer or decimal"); 4185 } 4186 Double e = Double.parseDouble(n1.get(0).primitiveValue()); 4187 Double d = Double.parseDouble(base.primitiveValue()); 4188 try { 4189 result.add(new DecimalType(Math.pow(d, e))); 4190 } catch (Exception ex) { 4191 // just return nothing 4192 } 4193 } else { 4194 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), 4195 "integer or decimal"); 4196 } 4197 return result; 4198 } 4199 4200 private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4201 if (focus.size() != 1) { 4202 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size()); 4203 } 4204 Base base = focus.get(0); 4205 List<Base> result = new ArrayList<Base>(); 4206 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4207 String s = base.primitiveValue(); 4208 if (s.contains(".")) { 4209 s = s.substring(0, s.indexOf(".")); 4210 } 4211 result.add(new IntegerType(s)); 4212 } else { 4213 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), 4214 "integer or decimal"); 4215 } 4216 return result; 4217 } 4218 4219 private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4220 if (focus.size() == 0) { 4221 return makeNull(); 4222 } 4223 if (focus.size() > 1) { 4224 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size()); 4225 } 4226 int precision = 0; 4227 if (expr.getParameters().size() > 0) { 4228 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4229 if (n1.size() != 1) { 4230 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", 4231 "integer"); 4232 } 4233 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4234 } 4235 4236 Base base = focus.get(0); 4237 List<Base> result = new ArrayList<Base>(); 4238 4239 if (base.hasType("decimal")) { 4240 result 4241 .add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); 4242 } else if (base.hasType("date")) { 4243 result 4244 .add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); 4245 } else if (base.hasType("dateTime")) { 4246 result 4247 .add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); 4248 } else if (base.hasType("time")) { 4249 result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 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 == 0 ? 8 : precision))); 4254 result.add(v); 4255 } else { 4256 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), 4257 "decimal or date"); 4258 } 4259 return result; 4260 } 4261 4262 private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4263 if (focus.size() == 0) { 4264 return makeNull(); 4265 } 4266 if (focus.size() > 1) { 4267 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4268 } 4269 int precision = 0; 4270 if (expr.getParameters().size() > 0) { 4271 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4272 if (n1.size() != 1) { 4273 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", 4274 "integer"); 4275 } 4276 precision = Integer.parseInt(n1.get(0).primitiveValue()); 4277 } 4278 4279 Base base = focus.get(0); 4280 List<Base> result = new ArrayList<Base>(); 4281 if (base.hasType("decimal")) { 4282 result.add( 4283 new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == 0 ? 8 : precision))); 4284 } else if (base.hasType("date")) { 4285 result 4286 .add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 10 : precision))); 4287 } else if (base.hasType("dateTime")) { 4288 result 4289 .add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision))); 4290 } else if (base.hasType("time")) { 4291 result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision))); 4292 } else if (base.hasType("Quantity")) { 4293 String value = getNamedValue(base, "value"); 4294 Base v = base.copy(); 4295 v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == 0 ? 8 : precision))); 4296 result.add(v); 4297 } else { 4298 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), 4299 "decimal or date"); 4300 } 4301 return result; 4302 } 4303 4304 private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4305 if (focus.size() != 1) { 4306 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size()); 4307 } 4308 Base base = focus.get(0); 4309 List<Base> result = new ArrayList<Base>(); 4310 if (base.hasType("decimal")) { 4311 result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue()))); 4312 } else if (base.hasType("date") || base.hasType("dateTime")) { 4313 result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue()))); 4314 } else if (base.hasType("time")) { 4315 result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue()))); 4316 } else { 4317 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), 4318 "decimal or date"); 4319 } 4320 return result; 4321 } 4322 4323 private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4324 if (focus.size() != 1) { 4325 throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size()); 4326 } 4327 Base base = focus.get(0); 4328 List<Base> result = new ArrayList<Base>(); 4329 if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 4330 int i = 0; 4331 if (expr.getParameters().size() == 1) { 4332 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 4333 if (n1.size() != 1) { 4334 throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", 4335 "integer"); 4336 } 4337 i = Integer.parseInt(n1.get(0).primitiveValue()); 4338 } 4339 BigDecimal d = new BigDecimal(base.primitiveValue()); 4340 result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP))); 4341 } else { 4342 makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), 4343 "integer or decimal"); 4344 } 4345 return result; 4346 } 4347 4348 private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 4349 4350 public static String bytesToHex(byte[] bytes) { 4351 char[] hexChars = new char[bytes.length * 2]; 4352 for (int j = 0; j < bytes.length; j++) { 4353 int v = bytes[j] & 0xFF; 4354 hexChars[j * 2] = HEX_ARRAY[v >>> 4]; 4355 hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 4356 } 4357 return new String(hexChars); 4358 } 4359 4360 public static byte[] hexStringToByteArray(String s) { 4361 int len = s.length(); 4362 byte[] data = new byte[len / 2]; 4363 for (int i = 0; i < len; i += 2) { 4364 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); 4365 } 4366 return data; 4367 } 4368 4369 private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4370 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4371 String param = nl.get(0).primitiveValue(); 4372 4373 List<Base> result = new ArrayList<Base>(); 4374 4375 if (focus.size() == 1) { 4376 String cnt = focus.get(0).primitiveValue(); 4377 if ("hex".equals(param)) { 4378 result.add(new StringType(bytesToHex(cnt.getBytes()))); 4379 } else if ("base64".equals(param)) { 4380 Base64.Encoder enc = Base64.getEncoder(); 4381 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4382 } else if ("urlbase64".equals(param)) { 4383 Base64.Encoder enc = Base64.getUrlEncoder(); 4384 result.add(new StringType(enc.encodeToString(cnt.getBytes()))); 4385 } 4386 } 4387 return result; 4388 } 4389 4390 private List<Base> funcDecode(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4391 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4392 String param = nl.get(0).primitiveValue(); 4393 4394 List<Base> result = new ArrayList<Base>(); 4395 4396 if (focus.size() == 1) { 4397 String cnt = focus.get(0).primitiveValue(); 4398 if ("hex".equals(param)) { 4399 result.add(new StringType(new String(hexStringToByteArray(cnt)))); 4400 } else if ("base64".equals(param)) { 4401 Base64.Decoder enc = Base64.getDecoder(); 4402 result.add(new StringType(new String(enc.decode(cnt)))); 4403 } else if ("urlbase64".equals(param)) { 4404 Base64.Decoder enc = Base64.getUrlDecoder(); 4405 result.add(new StringType(new String(enc.decode(cnt)))); 4406 } 4407 } 4408 4409 return result; 4410 } 4411 4412 private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4413 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4414 String param = nl.get(0).primitiveValue(); 4415 4416 List<Base> result = new ArrayList<Base>(); 4417 if (focus.size() == 1) { 4418 String cnt = focus.get(0).primitiveValue(); 4419 if ("html".equals(param)) { 4420 result.add(new StringType(Utilities.escapeXml(cnt))); 4421 } else if ("json".equals(param)) { 4422 result.add(new StringType(Utilities.escapeJson(cnt))); 4423 } 4424 } 4425 4426 return result; 4427 } 4428 4429 private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4430 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4431 String param = nl.get(0).primitiveValue(); 4432 4433 List<Base> result = new ArrayList<Base>(); 4434 if (focus.size() == 1) { 4435 String cnt = focus.get(0).primitiveValue(); 4436 if ("html".equals(param)) { 4437 result.add(new StringType(Utilities.unescapeXml(cnt))); 4438 } else if ("json".equals(param)) { 4439 result.add(new StringType(Utilities.unescapeJson(cnt))); 4440 } 4441 } 4442 4443 return result; 4444 } 4445 4446 private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4447 List<Base> result = new ArrayList<Base>(); 4448 if (focus.size() == 1) { 4449 String cnt = focus.get(0).primitiveValue(); 4450 result.add(new StringType(cnt.trim())); 4451 } 4452 return result; 4453 } 4454 4455 private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4456 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4457 String param = nl.get(0).primitiveValue(); 4458 4459 List<Base> result = new ArrayList<Base>(); 4460 if (focus.size() == 1) { 4461 String cnt = focus.get(0).primitiveValue(); 4462 String[] sl = Utilities.simpleSplit(cnt, param); 4463 for (String s : sl) { 4464 result.add(new StringType(s)); 4465 } 4466 } 4467 return result; 4468 } 4469 4470 private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4471 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4472 String param = nl.get(0).primitiveValue(); 4473 String param2 = param; 4474 if (exp.getParameters().size() == 2) { 4475 nl = execute(context, focus, exp.getParameters().get(1), true); 4476 param2 = nl.get(0).primitiveValue(); 4477 } 4478 4479 List<Base> result = new ArrayList<Base>(); 4480 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2); 4481 for (Base i : focus) { 4482 b.append(i.primitiveValue()); 4483 } 4484 result.add(new StringType(b.toString())); 4485 return result; 4486 } 4487 4488 private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4489 throws FHIRException { 4490 // todo: actually check the HTML 4491 if (focus.size() != 1) { 4492 return makeBoolean(false); 4493 } 4494 XhtmlNode x = focus.get(0).getXhtml(); 4495 if (x == null) { 4496 return makeBoolean(false); 4497 } 4498 return makeBoolean(checkHtmlNames(x)); 4499 } 4500 4501 private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4502 throws FHIRException { 4503 // todo: actually check the HTML 4504 if (focus.size() != 1) { 4505 return makeBoolean(false); 4506 } 4507 XhtmlNode x = focus.get(0).getXhtml(); 4508 if (x == null) { 4509 return makeBoolean(false); 4510 } 4511 return makeBoolean(checkForContent(x)); 4512 } 4513 4514 private boolean checkForContent(XhtmlNode x) { 4515 if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) 4516 || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) { 4517 return true; 4518 } 4519 for (XhtmlNode c : x.getChildNodes()) { 4520 if (checkForContent(c)) { 4521 return true; 4522 } 4523 } 4524 return false; 4525 } 4526 4527 private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4528 throws FHIRException { 4529 if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) { 4530 return makeBoolean(false); 4531 } 4532 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4533 if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) { 4534 return makeBoolean(false); 4535 } 4536 String s1 = getNamedValue(focus.get(0), "system"); 4537 String u1 = getNamedValue(focus.get(0), "code"); 4538 String s2 = getNamedValue(nl.get(0), "system"); 4539 String u2 = getNamedValue(nl.get(0), "code"); 4540 4541 if (s1 == null || s2 == null || !s1.equals(s2)) { 4542 return makeBoolean(false); 4543 } 4544 if (u1 == null || u2 == null) { 4545 return makeBoolean(false); 4546 } 4547 if (u1.equals(u2)) { 4548 return makeBoolean(true); 4549 } 4550 if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) { 4551 try { 4552 return makeBoolean(worker.getUcumService().isComparable(u1, u2)); 4553 } catch (UcumException e) { 4554 return makeBoolean(false); 4555 } 4556 } else { 4557 return makeBoolean(false); 4558 } 4559 } 4560 4561 private String getNamedValue(Base base, String name) { 4562 Property p = base.getChildByName(name); 4563 if (p.hasValues() && p.getValues().size() == 1) { 4564 return p.getValues().get(0).primitiveValue(); 4565 } 4566 return null; 4567 } 4568 4569 private boolean checkHtmlNames(XhtmlNode node) { 4570 if (node.getNodeType() == NodeType.Comment) { 4571 if (node.getContent().startsWith("DOCTYPE")) 4572 return false; 4573 } 4574 if (node.getNodeType() == NodeType.Element) { 4575 if (!Utilities.existsInList(node.getName(), "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", 4576 "b", "em", "i", "strong", "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", 4577 "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", "ul", "ol", "li", "dl", "dt", "dd", "pre", 4578 "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", "code", "samp", "img", 4579 "map", "area")) { 4580 return false; 4581 } 4582 for (String an : node.getAttributes().keySet()) { 4583 boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, "title", "style", "class", "id", "idref", "lang", 4584 "xml:lang", "dir", "accesskey", "tabindex", 4585 // tables 4586 "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", 4587 "colspan") || 4588 4589 Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", 4590 "blockquote.cite", "q.cite", "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", 4591 "a.shape", "a.coords", "img.src", "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", 4592 "img.ismap", "map.name", "area.shape", "area.coords", "area.href", "area.nohref", "area.alt", 4593 "table.summary", "table.width", "table.border", "table.frame", "table.rules", "table.cellspacing", 4594 "table.cellpadding", "pre.space", "td.nowrap"); 4595 if (!ok) { 4596 return false; 4597 } 4598 } 4599 for (XhtmlNode c : node.getChildNodes()) { 4600 if (!checkHtmlNames(c)) { 4601 return false; 4602 } 4603 } 4604 } 4605 return true; 4606 } 4607 4608 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4609 List<Base> result = new ArrayList<Base>(); 4610 if (exp.getParameters().size() == 1) { 4611 List<Base> pc = new ArrayList<Base>(); 4612 boolean all = true; 4613 for (Base item : focus) { 4614 pc.clear(); 4615 pc.add(item); 4616 Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 4617 if (eq != Equality.True) { 4618 all = false; 4619 break; 4620 } 4621 } 4622 result.add(new BooleanType(all).noExtensions()); 4623 } else {// (exp.getParameters().size() == 0) { 4624 boolean all = true; 4625 for (Base item : focus) { 4626 Equality eq = asBool(item, true); 4627 if (eq != Equality.True) { 4628 all = false; 4629 break; 4630 } 4631 } 4632 result.add(new BooleanType(all).noExtensions()); 4633 } 4634 return result; 4635 } 4636 4637 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 4638 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, 4639 newThis); 4640 // append all of the defined variables from the context into the new context 4641 if (context.definedVariables != null) { 4642 for (String s : context.definedVariables.keySet()) { 4643 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4644 } 4645 } 4646 return newContext; 4647 } 4648 4649 private ExecutionContext contextForParameter(ExecutionContext context) { 4650 ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem); 4651 newContext.total = context.total; 4652 newContext.index = context.index; 4653 // append all of the defined variables from the context into the new context 4654 if (context.definedVariables != null) { 4655 for (String s : context.definedVariables.keySet()) { 4656 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4657 } 4658 } 4659 return newContext; 4660 } 4661 4662 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 4663 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 4664 // append all of the defined variables from the context into the new context 4665 if (context.definedVariables != null) { 4666 for (String s : context.definedVariables.keySet()) { 4667 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4668 } 4669 } 4670 return newContext; 4671 } 4672 4673 private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) { 4674 ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, context.thisItem); 4675 // append all of the defined variables from the context into the new context 4676 if (context.definedVariables != null) { 4677 for (String s : context.definedVariables.keySet()) { 4678 newContext.setDefinedVariable(s, context.definedVariables.get(s)); 4679 } 4680 } 4681 return newContext; 4682 } 4683 4684 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4685 List<Base> result = new ArrayList<Base>(); 4686 result.add(DateTimeType.now()); 4687 return result; 4688 } 4689 4690 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4691 List<Base> result = new ArrayList<Base>(); 4692 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 4693 return result; 4694 } 4695 4696 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4697 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 4698 if (nl.size() != 1 || focus.size() != 1) { 4699 return new ArrayList<Base>(); 4700 } 4701 4702 String url = nl.get(0).primitiveValue(); 4703 ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) 4704 : worker.fetchResource(ValueSet.class, url); 4705 if (vs == null) { 4706 return new ArrayList<Base>(); 4707 } 4708 Base l = focus.get(0); 4709 if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) { 4710 return makeBoolean( 4711 worker.validateCode(terminologyServiceOptions.withGuessSystem(), l.castToCoding(l), vs).isOk()); 4712 } else if (l.fhirType().equals("Coding")) { 4713 return makeBoolean(worker.validateCode(terminologyServiceOptions, l.castToCoding(l), vs).isOk()); 4714 } else if (l.fhirType().equals("CodeableConcept")) { 4715 return makeBoolean(worker.validateCode(terminologyServiceOptions, l.castToCodeableConcept(l), vs).isOk()); 4716 } else { 4717 // System.out.println("unknown type in funcMemberOf: "+l.fhirType()); 4718 return new ArrayList<Base>(); 4719 } 4720 } 4721 4722 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4723 throws FHIRException { 4724 List<Base> result = new ArrayList<Base>(); 4725 List<Base> current = new ArrayList<Base>(); 4726 current.addAll(focus); 4727 List<Base> added = new ArrayList<Base>(); 4728 boolean more = true; 4729 while (more) { 4730 added.clear(); 4731 for (Base item : current) { 4732 getChildrenByName(item, "*", added); 4733 } 4734 more = !added.isEmpty(); 4735 result.addAll(added); 4736 current.clear(); 4737 current.addAll(added); 4738 } 4739 return result; 4740 } 4741 4742 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4743 List<Base> result = new ArrayList<Base>(); 4744 for (Base b : focus) { 4745 getChildrenByName(b, "*", result); 4746 } 4747 return result; 4748 } 4749 4750 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) 4751 throws FHIRException, PathEngineException { 4752 List<Base> result = new ArrayList<Base>(); 4753 List<Base> tB = execute(context, focus, expr.getParameters().get(0), true); 4754 String t = convertToString(tB); 4755 List<Base> rB = execute(context, focus, expr.getParameters().get(1), true); 4756 String r = convertToString(rB); 4757 4758 if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) { 4759 // 4760 } else if (focus.size() == 1) { 4761 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4762 String f = convertToString(focus.get(0)); 4763 if (Utilities.noString(f)) { 4764 result.add(new StringType("")); 4765 } else { 4766 String n = f.replace(t, r); 4767 result.add(new StringType(n)); 4768 } 4769 } 4770 } else { 4771 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size()); 4772 } 4773 return result; 4774 } 4775 4776 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4777 throws FHIRException { 4778 List<Base> result = new ArrayList<Base>(); 4779 List<Base> regexB = execute(context, focus, exp.getParameters().get(0), true); 4780 String regex = convertToString(regexB); 4781 List<Base> replB = execute(context, focus, exp.getParameters().get(1), true); 4782 String repl = convertToString(replB); 4783 4784 if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) { 4785 // 4786 } else if (focus.size() == 1 && !Utilities.noString(regex)) { 4787 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4788 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); 4789 } 4790 } else { 4791 result.add(new StringType(convertToString(focus.get(0))).noExtensions()); 4792 } 4793 return result; 4794 } 4795 4796 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4797 List<Base> result = new ArrayList<Base>(); 4798 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 4799 String sw = convertToString(swb); 4800 4801 if (focus.size() == 0) { 4802 // 4803 } else if (swb.size() == 0) { 4804 // 4805 } else if (Utilities.noString(sw)) { 4806 result.add(new BooleanType(true).noExtensions()); 4807 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 4808 if (focus.size() == 1 && !Utilities.noString(sw)) { 4809 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 4810 } else { 4811 result.add(new BooleanType(false).noExtensions()); 4812 } 4813 } 4814 return result; 4815 } 4816 4817 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4818 List<Base> result = new ArrayList<Base>(); 4819 result.add(new StringType(convertToString(focus)).noExtensions()); 4820 return result; 4821 } 4822 4823 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4824 List<Base> result = new ArrayList<Base>(); 4825 if (focus.size() == 1) { 4826 if (focus.get(0) instanceof BooleanType) { 4827 result.add(focus.get(0)); 4828 } else if (focus.get(0) instanceof IntegerType) { 4829 int i = Integer.parseInt(focus.get(0).primitiveValue()); 4830 if (i == 0) { 4831 result.add(new BooleanType(false).noExtensions()); 4832 } else if (i == 1) { 4833 result.add(new BooleanType(true).noExtensions()); 4834 } 4835 } else if (focus.get(0) instanceof DecimalType) { 4836 if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) { 4837 result.add(new BooleanType(false).noExtensions()); 4838 } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) { 4839 result.add(new BooleanType(true).noExtensions()); 4840 } 4841 } else if (focus.get(0) instanceof StringType) { 4842 if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) { 4843 result.add(new BooleanType(true).noExtensions()); 4844 } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) { 4845 result.add(new BooleanType(false).noExtensions()); 4846 } 4847 } 4848 } 4849 return result; 4850 } 4851 4852 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 4853 List<Base> result = new ArrayList<Base>(); 4854 if (focus.size() == 1) { 4855 if (focus.get(0) instanceof Quantity) { 4856 result.add(focus.get(0)); 4857 } else if (focus.get(0) instanceof StringType) { 4858 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 4859 if (q != null) { 4860 result.add(q.noExtensions()); 4861 } 4862 } else if (focus.get(0) instanceof IntegerType) { 4863 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())) 4864 .setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 4865 } else if (focus.get(0) instanceof DecimalType) { 4866 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())) 4867 .setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 4868 } 4869 } 4870 return result; 4871 } 4872 4873 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4874 // List<Base> result = new ArrayList<Base>(); 4875 // result.add(new BooleanType(convertToBoolean(focus))); 4876 // return result; 4877 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime"); 4878 } 4879 4880 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4881 // List<Base> result = new ArrayList<Base>(); 4882 // result.add(new BooleanType(convertToBoolean(focus))); 4883 // return result; 4884 throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime"); 4885 } 4886 4887 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 4888 String s = convertToString(focus); 4889 List<Base> result = new ArrayList<Base>(); 4890 if (Utilities.isDecimal(s, true)) { 4891 result.add(new DecimalType(s).noExtensions()); 4892 } 4893 if ("true".equals(s)) { 4894 result.add(new DecimalType(1).noExtensions()); 4895 } 4896 if ("false".equals(s)) { 4897 result.add(new DecimalType(0).noExtensions()); 4898 } 4899 return result; 4900 } 4901 4902 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4903 if (focus.size() > 1) { 4904 throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size()); 4905 } 4906 4907 List<Base> n1 = execute(focus.isEmpty() ? context : changeThis(context, focus.get(0)), focus, exp.getParameters().get(0), true); 4908 Equality v = asBool(n1, exp); 4909 if (v == Equality.True) { 4910 return execute(context, focus, exp.getParameters().get(1), true); 4911 } else if (exp.getParameters().size() < 3) { 4912 return new ArrayList<Base>(); 4913 } else { 4914 return execute(context, focus, exp.getParameters().get(2), true); 4915 } 4916 } 4917 4918 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4919 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 4920 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 4921 4922 List<Base> result = new ArrayList<Base>(); 4923 for (int i = 0; i < Math.min(focus.size(), i1); i++) { 4924 result.add(focus.get(i)); 4925 } 4926 return result; 4927 } 4928 4929 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4930 List<Base> result = new ArrayList<Base>(); 4931 for (Base item : focus) { 4932 if (!doContains(result, item)) { 4933 result.add(item); 4934 } 4935 } 4936 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 4937 if (!doContains(result, item)) { 4938 result.add(item); 4939 } 4940 } 4941 return result; 4942 } 4943 4944 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4945 List<Base> result = new ArrayList<Base>(); 4946 for (Base item : focus) { 4947 result.add(item); 4948 } 4949 for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) { 4950 result.add(item); 4951 } 4952 return result; 4953 } 4954 4955 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) 4956 throws FHIRException { 4957 List<Base> result = new ArrayList<Base>(); 4958 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 4959 4960 for (Base item : focus) { 4961 if (!doContains(result, item) && doContains(other, item)) { 4962 result.add(item); 4963 } 4964 } 4965 return result; 4966 } 4967 4968 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 4969 List<Base> result = new ArrayList<Base>(); 4970 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 4971 4972 for (Base item : focus) { 4973 if (!doContains(other, item)) { 4974 result.add(item); 4975 } 4976 } 4977 return result; 4978 } 4979 4980 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) 4981 throws PathEngineException { 4982 if (focus.size() == 1) { 4983 return focus; 4984 } 4985 throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size()); 4986 } 4987 4988 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) 4989 throws PathEngineException { 4990 if (focus.size() == 0 || focus.size() > 1) { 4991 return makeNull(); 4992 } 4993 String ns = null; 4994 String n = null; 4995 4996 ExpressionNode texp = expr.getParameters().get(0); 4997 if (texp.getKind() != Kind.Name) { 4998 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is"); 4999 } 5000 if (texp.getInner() != null) { 5001 if (texp.getInner().getKind() != Kind.Name) { 5002 throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is"); 5003 } 5004 ns = texp.getName(); 5005 n = texp.getInner().getName(); 5006 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", 5007 "Time", "SimpleTypeInfo", "ClassInfo")) { 5008 ns = "System"; 5009 n = texp.getName(); 5010 } else { 5011 ns = "FHIR"; 5012 n = texp.getName(); 5013 } 5014 if (ns.equals("System")) { 5015 if (focus.get(0) instanceof Resource) { 5016 return makeBoolean(false); 5017 } 5018 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) { 5019 String t = Utilities.capitalize(focus.get(0).fhirType()); 5020 if (n.equals(t)) { 5021 return makeBoolean(true); 5022 } 5023 if ("Date".equals(t) && n.equals("DateTime")) { 5024 return makeBoolean(true); 5025 } else { 5026 return makeBoolean(false); 5027 } 5028 } else { 5029 return makeBoolean(false); 5030 } 5031 } else if (ns.equals("FHIR")) { 5032 if (n.equals(focus.get(0).fhirType())) { 5033 return makeBoolean(true); 5034 } else { 5035 StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); 5036 while (sd != null) { 5037 if (n.equals(sd.getType())) { 5038 return makeBoolean(true); 5039 } 5040 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 5041 } 5042 return makeBoolean(false); 5043 } 5044 } else { 5045 return makeBoolean(false); 5046 } 5047 } 5048 5049 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5050 List<Base> result = new ArrayList<Base>(); 5051 String tn; 5052 if (expr.getParameters().get(0).getInner() != null) { 5053 tn = expr.getParameters().get(0).getName() + "." + expr.getParameters().get(0).getInner().getName(); 5054 } else { 5055 tn = "FHIR." + expr.getParameters().get(0).getName(); 5056 } 5057 if (!isKnownType(tn)) { 5058 throw new PathEngineException("The type " + tn + " is not valid"); 5059 } 5060 if (!doNotEnforceAsSingletonRule && focus.size() > 1) { 5061 throw new PathEngineException("Attempt to use as() on more than one item (" + focus.size() + ")"); 5062 } 5063 5064 for (Base b : focus) { 5065 if (tn.startsWith("System.")) { 5066 if (b instanceof Element && ((Element) b).isDisallowExtensions()) { 5067 if (b.hasType(tn.substring(7))) { 5068 result.add(b); 5069 } 5070 } 5071 5072 } else if (tn.startsWith("FHIR.")) { 5073 String tnp = tn.substring(5); 5074 if (b.fhirType().equals(tnp)) { 5075 result.add(b); 5076 } else { 5077 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5078 while (sd != null) { 5079 if (compareTypeNames(tnp, sd.getType())) { 5080 result.add(b); 5081 break; 5082 } 5083 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null 5084 : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 5085 } 5086 } 5087 } 5088 } 5089 return result; 5090 } 5091 5092 private List<Base> funcOfType(ExecutionContext context, List<Base> focus, ExpressionNode expr) { 5093 List<Base> result = new ArrayList<Base>(); 5094 String tn; 5095 if (expr.getParameters().get(0).getInner() != null) { 5096 tn = expr.getParameters().get(0).getName() + "." + expr.getParameters().get(0).getInner().getName(); 5097 } else { 5098 tn = "FHIR." + expr.getParameters().get(0).getName(); 5099 } 5100 if (!isKnownType(tn)) { 5101 throw new PathEngineException("The type " + tn + " is not valid"); 5102 } 5103 5104 for (Base b : focus) { 5105 if (tn.startsWith("System.")) { 5106 if (b instanceof Element && ((Element) b).isDisallowExtensions()) { 5107 if (b.hasType(tn.substring(7))) { 5108 result.add(b); 5109 } 5110 } 5111 5112 } else if (tn.startsWith("FHIR.")) { 5113 String tnp = tn.substring(5); 5114 if (b.fhirType().equals(tnp)) { 5115 result.add(b); 5116 } else { 5117 StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType()); 5118 while (sd != null) { 5119 if (tnp.equals(sd.getType())) { 5120 result.add(b); 5121 break; 5122 } 5123 sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null 5124 : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 5125 } 5126 } 5127 } 5128 } 5129 return result; 5130 } 5131 5132 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5133 List<Base> result = new ArrayList<Base>(); 5134 for (Base item : focus) { 5135 result.add(new ClassTypeInfo(item)); 5136 } 5137 return result; 5138 } 5139 5140 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5141 List<Base> result = new ArrayList<Base>(); 5142 List<Base> current = new ArrayList<Base>(); 5143 current.addAll(focus); 5144 List<Base> added = new ArrayList<Base>(); 5145 boolean more = true; 5146 while (more) { 5147 added.clear(); 5148 List<Base> pc = new ArrayList<Base>(); 5149 for (Base item : current) { 5150 pc.clear(); 5151 pc.add(item); 5152 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 5153 } 5154 more = false; 5155 current.clear(); 5156 for (Base b : added) { 5157 boolean isnew = true; 5158 for (Base t : result) { 5159 if (b.equalsDeep(t)) { 5160 isnew = false; 5161 } 5162 } 5163 if (isnew) { 5164 result.add(b); 5165 current.add(b); 5166 more = true; 5167 } 5168 } 5169 } 5170 return result; 5171 } 5172 5173 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5174 throws FHIRException { 5175 List<Base> total = new ArrayList<Base>(); 5176 if (exp.parameterCount() > 1) { 5177 total = execute(context, focus, exp.getParameters().get(1), false); 5178 } 5179 5180 List<Base> pc = new ArrayList<Base>(); 5181 for (Base item : focus) { 5182 ExecutionContext c = changeThis(context, item); 5183 c.total = total; 5184 c.next(); 5185 total = execute(c, pc, exp.getParameters().get(0), true); 5186 } 5187 return total; 5188 } 5189 5190 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5191 if (focus.size() < 1) { 5192 return makeBoolean(true); 5193 } 5194 if (focus.size() == 1) { 5195 return makeBoolean(true); 5196 } 5197 5198 boolean distinct = true; 5199 for (int i = 0; i < focus.size(); i++) { 5200 for (int j = i + 1; j < focus.size(); j++) { 5201 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5202 if (eq == null) { 5203 return new ArrayList<Base>(); 5204 } else if (eq == true) { 5205 distinct = false; 5206 break; 5207 } 5208 } 5209 } 5210 return makeBoolean(distinct); 5211 } 5212 5213 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5214 throws FHIRException { 5215 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 5216 5217 boolean valid = true; 5218 for (Base item : target) { 5219 boolean found = false; 5220 for (Base t : focus) { 5221 if (Base.compareDeep(item, t, false)) { 5222 found = true; 5223 break; 5224 } 5225 } 5226 if (!found) { 5227 valid = false; 5228 break; 5229 } 5230 } 5231 List<Base> result = new ArrayList<Base>(); 5232 result.add(new BooleanType(valid).noExtensions()); 5233 return result; 5234 } 5235 5236 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5237 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 5238 5239 boolean valid = true; 5240 for (Base item : focus) { 5241 boolean found = false; 5242 for (Base t : target) { 5243 if (Base.compareDeep(item, t, false)) { 5244 found = true; 5245 break; 5246 } 5247 } 5248 if (!found) { 5249 valid = false; 5250 break; 5251 } 5252 } 5253 List<Base> result = new ArrayList<Base>(); 5254 result.add(new BooleanType(valid).noExtensions()); 5255 return result; 5256 } 5257 5258 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5259 List<Base> result = new ArrayList<Base>(); 5260 boolean empty = true; 5261 List<Base> pc = new ArrayList<Base>(); 5262 for (Base f : focus) { 5263 if (exp.getParameters().size() == 1) { 5264 pc.clear(); 5265 pc.add(f); 5266 Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp); 5267 if (v == Equality.True) { 5268 empty = false; 5269 } 5270 } else if (!f.isEmpty()) { 5271 empty = false; 5272 } 5273 } 5274 result.add(new BooleanType(!empty).noExtensions()); 5275 return result; 5276 } 5277 5278 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5279 List<Base> result = new ArrayList<Base>(); 5280 Base refContext = null; 5281 for (Base item : focus) { 5282 String s = convertToString(item); 5283 if (item.fhirType().equals("Reference")) { 5284 refContext = item; 5285 Property p = item.getChildByName("reference"); 5286 if (p != null && p.hasValues()) { 5287 s = convertToString(p.getValues().get(0)); 5288 } else { 5289 s = null; // a reference without any valid actual reference (just identifier or display, 5290 // but we can't resolve it) 5291 } 5292 } 5293 if (item.fhirType().equals("canonical")) { 5294 s = item.primitiveValue(); 5295 refContext = item; 5296 } 5297 if (s != null) { 5298 Base res = null; 5299 if (s.startsWith("#")) { 5300 Property p = context.rootResource.getChildByName("contained"); 5301 if (p != null) { 5302 for (Base c : p.getValues()) { 5303 if (chompHash(s).equals(chompHash(c.getIdBase()))) { 5304 res = c; 5305 break; 5306 } 5307 } 5308 } 5309 } else if (hostServices != null) { 5310 try { 5311 res = hostServices.resolveReference(this, context.appInfo, s, refContext); 5312 } catch (Exception e) { 5313 res = null; 5314 } 5315 } 5316 if (res != null) { 5317 result.add(res); 5318 } 5319 } 5320 } 5321 5322 return result; 5323 } 5324 5325 /** 5326 * Strips a leading hashmark (#) if present at the start of a string 5327 */ 5328 private String chompHash(String theId) { 5329 String retVal = theId; 5330 while (retVal.startsWith("#")) { 5331 retVal = retVal.substring(1); 5332 } 5333 return retVal; 5334 } 5335 5336 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5337 throws FHIRException { 5338 List<Base> result = new ArrayList<Base>(); 5339 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5340 String url = nl.get(0).primitiveValue(); 5341 5342 for (Base item : focus) { 5343 List<Base> ext = new ArrayList<Base>(); 5344 getChildrenByName(item, "extension", ext); 5345 getChildrenByName(item, "modifierExtension", ext); 5346 for (Base ex : ext) { 5347 List<Base> vl = new ArrayList<Base>(); 5348 getChildrenByName(ex, "url", vl); 5349 if (convertToString(vl).equals(url)) { 5350 result.add(ex); 5351 } 5352 } 5353 } 5354 return result; 5355 } 5356 5357 private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5358 List<Base> result = new ArrayList<Base>(); 5359 if (exp.getParameters().size() == 1) { 5360 boolean all = true; 5361 List<Base> pc = new ArrayList<Base>(); 5362 for (Base item : focus) { 5363 pc.clear(); 5364 pc.add(item); 5365 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5366 Equality v = asBool(res, exp); 5367 if (v != Equality.False) { 5368 all = false; 5369 break; 5370 } 5371 } 5372 result.add(new BooleanType(all).noExtensions()); 5373 } else { 5374 boolean all = true; 5375 for (Base item : focus) { 5376 if (!canConvertToBoolean(item)) { 5377 throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); 5378 } 5379 5380 Equality v = asBool(item, true); 5381 if (v != Equality.False) { 5382 all = false; 5383 break; 5384 } 5385 } 5386 result.add(new BooleanType(all).noExtensions()); 5387 } 5388 return result; 5389 } 5390 5391 private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5392 List<Base> result = new ArrayList<Base>(); 5393 if (exp.getParameters().size() == 1) { 5394 boolean any = false; 5395 List<Base> pc = new ArrayList<Base>(); 5396 for (Base item : focus) { 5397 pc.clear(); 5398 pc.add(item); 5399 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5400 Equality v = asBool(res, exp); 5401 if (v == Equality.False) { 5402 any = true; 5403 break; 5404 } 5405 } 5406 result.add(new BooleanType(any).noExtensions()); 5407 } else { 5408 boolean any = false; 5409 for (Base item : focus) { 5410 if (!canConvertToBoolean(item)) { 5411 throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); 5412 } 5413 5414 Equality v = asBool(item, true); 5415 if (v == Equality.False) { 5416 any = true; 5417 break; 5418 } 5419 } 5420 result.add(new BooleanType(any).noExtensions()); 5421 } 5422 return result; 5423 } 5424 5425 private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5426 List<Base> result = new ArrayList<Base>(); 5427 if (exp.getParameters().size() == 1) { 5428 boolean all = true; 5429 List<Base> pc = new ArrayList<Base>(); 5430 for (Base item : focus) { 5431 pc.clear(); 5432 pc.add(item); 5433 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5434 Equality v = asBool(res, exp); 5435 if (v != Equality.True) { 5436 all = false; 5437 break; 5438 } 5439 } 5440 result.add(new BooleanType(all).noExtensions()); 5441 } else { 5442 boolean all = true; 5443 for (Base item : focus) { 5444 if (!canConvertToBoolean(item)) { 5445 throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); 5446 } 5447 Equality v = asBool(item, true); 5448 if (v != Equality.True) { 5449 all = false; 5450 break; 5451 } 5452 } 5453 result.add(new BooleanType(all).noExtensions()); 5454 } 5455 return result; 5456 } 5457 5458 private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5459 List<Base> result = new ArrayList<Base>(); 5460 if (exp.getParameters().size() == 1) { 5461 boolean any = false; 5462 List<Base> pc = new ArrayList<Base>(); 5463 for (Base item : focus) { 5464 pc.clear(); 5465 pc.add(item); 5466 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 5467 Equality v = asBool(res, exp); 5468 if (v == Equality.True) { 5469 any = true; 5470 break; 5471 } 5472 } 5473 result.add(new BooleanType(any).noExtensions()); 5474 } else { 5475 boolean any = false; 5476 for (Base item : focus) { 5477 if (!canConvertToBoolean(item)) { 5478 throw new FHIRException("Unable to convert '" + convertToString(item) + "' to a boolean"); 5479 } 5480 5481 Equality v = asBool(item, true); 5482 if (v == Equality.True) { 5483 any = true; 5484 break; 5485 } 5486 } 5487 result.add(new BooleanType(any).noExtensions()); 5488 } 5489 return result; 5490 } 5491 5492 private boolean canConvertToBoolean(Base item) { 5493 return (item.isBooleanPrimitive()); 5494 } 5495 5496 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5497 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5498 String name = nl.get(0).primitiveValue(); 5499 if (exp.getParameters().size() == 2) { 5500 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 5501 log(name, n2); 5502 } else { 5503 log(name, focus); 5504 } 5505 return focus; 5506 } 5507 5508 private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5509 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 5510 String name = nl.get(0).primitiveValue(); 5511 List<Base> value; 5512 if (exp.getParameters().size() == 2) { 5513 value = execute(context, focus, exp.getParameters().get(1), true); 5514 } else { 5515 value = focus; 5516 } 5517 // stash the variable into the context 5518 context.setDefinedVariable(name, value); 5519 return focus; 5520 } 5521 5522 private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException { 5523 List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true); 5524 if (!convertToBoolean(n1)) { 5525 List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true); 5526 String name = n2.get(0).primitiveValue(); 5527 throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name); 5528 } 5529 return focus; 5530 } 5531 5532 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5533 if (focus.size() <= 1) { 5534 return focus; 5535 } 5536 5537 List<Base> result = new ArrayList<Base>(); 5538 for (int i = 0; i < focus.size(); i++) { 5539 boolean found = false; 5540 for (int j = i + 1; j < focus.size(); j++) { 5541 Boolean eq = doEquals(focus.get(j), focus.get(i)); 5542 if (eq == null) 5543 return new ArrayList<Base>(); 5544 else if (eq == true) { 5545 found = true; 5546 break; 5547 } 5548 } 5549 if (!found) { 5550 result.add(focus.get(i)); 5551 } 5552 } 5553 return result; 5554 } 5555 5556 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5557 List<Base> result = new ArrayList<Base>(); 5558 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5559 String sw = convertToString(swb); 5560 5561 if (focus.size() == 0 || swb.size() == 0) { 5562 // 5563 } else if (focus.size() == 1 && !Utilities.noString(sw)) { 5564 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5565 String st = convertToString(focus.get(0)); 5566 if (Utilities.noString(st)) { 5567 result.add(new BooleanType(false).noExtensions()); 5568 } else { 5569 Pattern p = Pattern.compile("(?s)" + sw); 5570 Matcher m = p.matcher(st); 5571 boolean ok = m.find(); 5572 result.add(new BooleanType(ok).noExtensions()); 5573 } 5574 } 5575 } else { 5576 result.add(new BooleanType(false).noExtensions()); 5577 } 5578 return result; 5579 } 5580 5581 private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5582 throws FHIRException { 5583 List<Base> result = new ArrayList<Base>(); 5584 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 5585 5586 if (focus.size() == 1 && !Utilities.noString(sw)) { 5587 if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5588 String st = convertToString(focus.get(0)); 5589 if (Utilities.noString(st)) { 5590 result.add(new BooleanType(false).noExtensions()); 5591 } else { 5592 Pattern p = Pattern.compile("(?s)" + sw); 5593 Matcher m = p.matcher(st); 5594 boolean ok = m.matches(); 5595 result.add(new BooleanType(ok).noExtensions()); 5596 } 5597 } 5598 } else { 5599 result.add(new BooleanType(false).noExtensions()); 5600 } 5601 return result; 5602 } 5603 5604 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5605 List<Base> result = new ArrayList<Base>(); 5606 List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true); 5607 String sw = convertToString(swb); 5608 5609 if (focus.size() != 1) { 5610 // 5611 } else if (swb.size() != 1) { 5612 // 5613 } else if (Utilities.noString(sw)) { 5614 result.add(new BooleanType(true).noExtensions()); 5615 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5616 String st = convertToString(focus.get(0)); 5617 if (Utilities.noString(st)) { 5618 result.add(new BooleanType(false).noExtensions()); 5619 } else { 5620 result.add(new BooleanType(st.contains(sw)).noExtensions()); 5621 } 5622 } 5623 return result; 5624 } 5625 5626 private List<Base> baseToList(Base b) { 5627 List<Base> res = new ArrayList<>(); 5628 res.add(b); 5629 return res; 5630 } 5631 5632 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5633 List<Base> result = new ArrayList<Base>(); 5634 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5635 String s = convertToString(focus.get(0)); 5636 result.add(new IntegerType(s.length()).noExtensions()); 5637 } 5638 return result; 5639 } 5640 5641 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5642 List<Base> result = new ArrayList<Base>(); 5643 if (focus.size() == 1) { 5644 String s = convertToString(focus.get(0)); 5645 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 5646 } else { 5647 result.add(new BooleanType(false).noExtensions()); 5648 } 5649 return result; 5650 } 5651 5652 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5653 throws FHIRException { 5654 List<Base> result = new ArrayList<Base>(); 5655 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5656 String sw = convertToString(swb); 5657 5658 if (focus.size() == 0) { 5659 // no result 5660 } else if (swb.size() == 0) { 5661 // no result 5662 } else if (Utilities.noString(sw)) { 5663 result.add(new BooleanType(true).noExtensions()); 5664 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5665 String s = convertToString(focus.get(0)); 5666 if (s == null) { 5667 result.add(new BooleanType(false).noExtensions()); 5668 } else { 5669 result.add(new BooleanType(s.startsWith(sw)).noExtensions()); 5670 } 5671 } 5672 return result; 5673 } 5674 5675 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5676 List<Base> result = new ArrayList<Base>(); 5677 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5678 String s = convertToString(focus.get(0)); 5679 if (!Utilities.noString(s)) { 5680 result.add(new StringType(s.toLowerCase()).noExtensions()); 5681 } 5682 } 5683 return result; 5684 } 5685 5686 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5687 List<Base> result = new ArrayList<Base>(); 5688 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5689 String s = convertToString(focus.get(0)); 5690 if (!Utilities.noString(s)) { 5691 result.add(new StringType(s.toUpperCase()).noExtensions()); 5692 } 5693 } 5694 return result; 5695 } 5696 5697 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5698 List<Base> result = new ArrayList<Base>(); 5699 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5700 String s = convertToString(focus.get(0)); 5701 for (char c : s.toCharArray()) { 5702 result.add(new StringType(String.valueOf(c)).noExtensions()); 5703 } 5704 } 5705 return result; 5706 } 5707 5708 private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5709 List<Base> result = new ArrayList<Base>(); 5710 5711 List<Base> swb = execute(context, focus, exp.getParameters().get(0), true); 5712 String sw = convertToString(swb); 5713 if (focus.size() == 0) { 5714 // no result 5715 } else if (swb.size() == 0) { 5716 // no result 5717 } else if (Utilities.noString(sw)) { 5718 result.add(new IntegerType(0).noExtensions()); 5719 } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) { 5720 String s = convertToString(focus.get(0)); 5721 if (s == null) { 5722 result.add(new IntegerType(0).noExtensions()); 5723 } else { 5724 result.add(new IntegerType(s.indexOf(sw)).noExtensions()); 5725 } 5726 } 5727 return result; 5728 } 5729 5730 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) 5731 throws FHIRException { 5732 List<Base> result = new ArrayList<Base>(); 5733 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 5734 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 5735 int i2 = -1; 5736 if (exp.parameterCount() == 2) { 5737 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 5738 if (n2.isEmpty() || !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) { 5739 return new ArrayList<Base>(); 5740 } 5741 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 5742 } 5743 5744 if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) { 5745 String sw = convertToString(focus.get(0)); 5746 String s; 5747 if (i1 < 0 || i1 >= sw.length()) { 5748 return new ArrayList<Base>(); 5749 } 5750 if (exp.parameterCount() == 2) { 5751 s = sw.substring(i1, Math.min(sw.length(), i1 + i2)); 5752 } else { 5753 s = sw.substring(i1); 5754 } 5755 if (!Utilities.noString(s)) { 5756 result.add(new StringType(s).noExtensions()); 5757 } 5758 } 5759 return result; 5760 } 5761 5762 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5763 String s = convertToString(focus); 5764 List<Base> result = new ArrayList<Base>(); 5765 if (Utilities.isInteger(s)) { 5766 result.add(new IntegerType(s).noExtensions()); 5767 } else if ("true".equals(s)) { 5768 result.add(new IntegerType(1).noExtensions()); 5769 } else if ("false".equals(s)) { 5770 result.add(new IntegerType(0).noExtensions()); 5771 } 5772 return result; 5773 } 5774 5775 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5776 List<Base> result = new ArrayList<Base>(); 5777 if (focus.size() != 1) { 5778 result.add(new BooleanType(false).noExtensions()); 5779 } else if (focus.get(0) instanceof IntegerType) { 5780 result.add(new BooleanType(true).noExtensions()); 5781 } else if (focus.get(0) instanceof BooleanType) { 5782 result.add(new BooleanType(true).noExtensions()); 5783 } else if (focus.get(0) instanceof StringType) { 5784 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 5785 } else { 5786 result.add(new BooleanType(false).noExtensions()); 5787 } 5788 return result; 5789 } 5790 5791 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5792 List<Base> result = new ArrayList<Base>(); 5793 if (focus.size() != 1) { 5794 result.add(new BooleanType(false).noExtensions()); 5795 } else if (focus.get(0) instanceof IntegerType) { 5796 result.add( 5797 new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1) 5798 .noExtensions()); 5799 } else if (focus.get(0) instanceof DecimalType) { 5800 result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 5801 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); 5802 } else if (focus.get(0) instanceof BooleanType) { 5803 result.add(new BooleanType(true).noExtensions()); 5804 } else if (focus.get(0) instanceof StringType) { 5805 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")) 5806 .noExtensions()); 5807 } else { 5808 result.add(new BooleanType(false).noExtensions()); 5809 } 5810 return result; 5811 } 5812 5813 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5814 List<Base> result = new ArrayList<Base>(); 5815 if (focus.size() != 1) { 5816 result.add(new BooleanType(false).noExtensions()); 5817 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 5818 result.add(new BooleanType(true).noExtensions()); 5819 } else if (focus.get(0) instanceof StringType) { 5820 result.add(new BooleanType((convertToString(focus.get(0)).matches( 5821 "([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))?)?)?)?"))) 5822 .noExtensions()); 5823 } else { 5824 result.add(new BooleanType(false).noExtensions()); 5825 } 5826 return result; 5827 } 5828 5829 private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5830 List<Base> result = new ArrayList<Base>(); 5831 if (focus.size() != 1) { 5832 result.add(new BooleanType(false).noExtensions()); 5833 } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) { 5834 result.add(new BooleanType(true).noExtensions()); 5835 } else if (focus.get(0) instanceof StringType) { 5836 result.add(new BooleanType((convertToString(focus.get(0)).matches( 5837 "([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))?)?)?)?"))) 5838 .noExtensions()); 5839 } else { 5840 result.add(new BooleanType(false).noExtensions()); 5841 } 5842 return result; 5843 } 5844 5845 private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) 5846 throws FHIRException { 5847 if (hostServices == null) { 5848 throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo"); 5849 } 5850 List<Base> result = new ArrayList<Base>(); 5851 if (focus.size() != 1) { 5852 result.add(new BooleanType(false).noExtensions()); 5853 } else { 5854 String url = convertToString(execute(context, focus, expr.getParameters().get(0), true)); 5855 result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo, focus.get(0), url)).noExtensions()); 5856 } 5857 return result; 5858 } 5859 5860 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5861 List<Base> result = new ArrayList<Base>(); 5862 if (focus.size() != 1) { 5863 result.add(new BooleanType(false).noExtensions()); 5864 } else if (focus.get(0) instanceof TimeType) { 5865 result.add(new BooleanType(true).noExtensions()); 5866 } else if (focus.get(0) instanceof StringType) { 5867 result.add(new BooleanType((convertToString(focus.get(0)).matches( 5868 "(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))?"))) 5869 .noExtensions()); 5870 } else { 5871 result.add(new BooleanType(false).noExtensions()); 5872 } 5873 return result; 5874 } 5875 5876 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5877 List<Base> result = new ArrayList<Base>(); 5878 if (focus.size() != 1) { 5879 result.add(new BooleanType(false).noExtensions()); 5880 } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) { 5881 result.add(new BooleanType(true).noExtensions()); 5882 } else { 5883 result.add(new BooleanType(false).noExtensions()); 5884 } 5885 return result; 5886 } 5887 5888 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5889 List<Base> result = new ArrayList<Base>(); 5890 if (focus.size() != 1) { 5891 result.add(new BooleanType(false).noExtensions()); 5892 } else if (focus.get(0) instanceof IntegerType) { 5893 result.add(new BooleanType(true).noExtensions()); 5894 } else if (focus.get(0) instanceof DecimalType) { 5895 result.add(new BooleanType(true).noExtensions()); 5896 } else if (focus.get(0) instanceof Quantity) { 5897 result.add(new BooleanType(true).noExtensions()); 5898 } else if (focus.get(0) instanceof BooleanType) { 5899 result.add(new BooleanType(true).noExtensions()); 5900 } else if (focus.get(0) instanceof StringType) { 5901 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 5902 result.add(new BooleanType(q != null).noExtensions()); 5903 } else { 5904 result.add(new BooleanType(false).noExtensions()); 5905 } 5906 return result; 5907 } 5908 5909 public Quantity parseQuantityString(String s) { 5910 if (s == null) { 5911 return null; 5912 } 5913 s = s.trim(); 5914 if (s.contains(" ")) { 5915 String v = s.substring(0, s.indexOf(" ")).trim(); 5916 s = s.substring(s.indexOf(" ")).trim(); 5917 if (!Utilities.isDecimal(v, false)) { 5918 return null; 5919 } 5920 if (s.startsWith("'") && s.endsWith("'")) { 5921 return Quantity.fromUcum(v, s.substring(1, s.length() - 1)); 5922 } 5923 if (s.equals("year") || s.equals("years")) { 5924 return Quantity.fromUcum(v, "a"); 5925 } else if (s.equals("month") || s.equals("months")) { 5926 return Quantity.fromUcum(v, "mo_s"); 5927 } else if (s.equals("week") || s.equals("weeks")) { 5928 return Quantity.fromUcum(v, "wk"); 5929 } else if (s.equals("day") || s.equals("days")) { 5930 return Quantity.fromUcum(v, "d"); 5931 } else if (s.equals("hour") || s.equals("hours")) { 5932 return Quantity.fromUcum(v, "h"); 5933 } else if (s.equals("minute") || s.equals("minutes")) { 5934 return Quantity.fromUcum(v, "min"); 5935 } else if (s.equals("second") || s.equals("seconds")) { 5936 return Quantity.fromUcum(v, "s"); 5937 } else if (s.equals("millisecond") || s.equals("milliseconds")) { 5938 return Quantity.fromUcum(v, "ms"); 5939 } else { 5940 return null; 5941 } 5942 } else { 5943 if (Utilities.isDecimal(s, true)) { 5944 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 5945 } else { 5946 return null; 5947 } 5948 } 5949 } 5950 5951 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5952 List<Base> result = new ArrayList<Base>(); 5953 if (focus.size() != 1) { 5954 result.add(new BooleanType(false).noExtensions()); 5955 } else if (focus.get(0) instanceof IntegerType) { 5956 result.add(new BooleanType(true).noExtensions()); 5957 } else if (focus.get(0) instanceof BooleanType) { 5958 result.add(new BooleanType(true).noExtensions()); 5959 } else if (focus.get(0) instanceof DecimalType) { 5960 result.add(new BooleanType(true).noExtensions()); 5961 } else if (focus.get(0) instanceof StringType) { 5962 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions()); 5963 } else { 5964 result.add(new BooleanType(false).noExtensions()); 5965 } 5966 return result; 5967 } 5968 5969 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5970 List<Base> result = new ArrayList<Base>(); 5971 result.add(new IntegerType(focus.size()).noExtensions()); 5972 return result; 5973 } 5974 5975 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 5976 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 5977 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 5978 5979 List<Base> result = new ArrayList<Base>(); 5980 for (int i = i1; i < focus.size(); i++) { 5981 result.add(focus.get(i)); 5982 } 5983 return result; 5984 } 5985 5986 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5987 List<Base> result = new ArrayList<Base>(); 5988 for (int i = 1; i < focus.size(); i++) { 5989 result.add(focus.get(i)); 5990 } 5991 return result; 5992 } 5993 5994 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 5995 List<Base> result = new ArrayList<Base>(); 5996 if (focus.size() > 0) { 5997 result.add(focus.get(focus.size() - 1)); 5998 } 5999 return result; 6000 } 6001 6002 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6003 List<Base> result = new ArrayList<Base>(); 6004 if (focus.size() > 0) { 6005 result.add(focus.get(0)); 6006 } 6007 return result; 6008 } 6009 6010 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6011 List<Base> result = new ArrayList<Base>(); 6012 List<Base> pc = new ArrayList<Base>(); 6013 for (Base item : focus) { 6014 pc.clear(); 6015 pc.add(item); 6016 Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp); 6017 if (v == Equality.True) { 6018 result.add(item); 6019 } 6020 } 6021 return result; 6022 } 6023 6024 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6025 List<Base> result = new ArrayList<Base>(); 6026 List<Base> pc = new ArrayList<Base>(); 6027 int i = 0; 6028 for (Base item : focus) { 6029 pc.clear(); 6030 pc.add(item); 6031 result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true)); 6032 i++; 6033 } 6034 return result; 6035 } 6036 6037 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 6038 List<Base> result = new ArrayList<Base>(); 6039 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 6040 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) { 6041 result.add(focus.get(Integer.parseInt(s))); 6042 } 6043 return result; 6044 } 6045 6046 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 6047 List<Base> result = new ArrayList<Base>(); 6048 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 6049 return result; 6050 } 6051 6052 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) 6053 throws PathEngineException { 6054 List<Base> result = new ArrayList<Base>(); 6055 Equality v = asBool(focus, exp); 6056 if (v != Equality.Null) { 6057 result.add(new BooleanType(v != Equality.True)); 6058 } 6059 return result; 6060 } 6061 6062 private class ElementDefinitionMatch { 6063 private ElementDefinition definition; 6064 private String fixedType; 6065 6066 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 6067 super(); 6068 this.definition = definition; 6069 this.fixedType = fixedType; 6070 } 6071 6072 public ElementDefinition getDefinition() { 6073 return definition; 6074 } 6075 6076 public String getFixedType() { 6077 return fixedType; 6078 } 6079 6080 } 6081 6082 private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr) 6083 throws PathEngineException, DefinitionException { 6084 if (Utilities.noString(type)) { 6085 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName"); 6086 } 6087 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) { 6088 return; 6089 } 6090 if (type.startsWith(Constants.NS_SYSTEM_TYPE)) { 6091 return; 6092 } 6093 6094 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 6095 getSimpleTypeChildTypesByName(name, result); 6096 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 6097 getClassInfoChildTypesByName(name, result); 6098 } else { 6099 String url = null; 6100 if (type.contains("#")) { 6101 url = type.substring(0, type.indexOf("#")); 6102 } else { 6103 url = type; 6104 } 6105 String tail = ""; 6106 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 6107 if (sd == null) { 6108 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName"); 6109 } 6110 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 6111 ElementDefinitionMatch m = null; 6112 if (type.contains("#")) 6113 m = getElementDefinition(sd, type.substring(type.indexOf("#") + 1), false, expr); 6114 if (m != null && hasDataType(m.definition)) { 6115 if (m.fixedType != null) { 6116 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, 6117 ProfileUtilities.sdNs(m.fixedType, null)); 6118 if (dt == null) { 6119 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(m.fixedType, null), 6120 "getChildTypesByName"); 6121 } 6122 sdl.add(dt); 6123 } else 6124 for (TypeRefComponent t : m.definition.getType()) { 6125 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, 6126 ProfileUtilities.sdNs(t.getCode(), null)); 6127 if (dt == null) { 6128 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), null), 6129 "getChildTypesByName"); 6130 } 6131 addTypeAndDescendents(sdl, dt, worker.allStructures()); 6132 // also add any descendant types 6133 } 6134 } else { 6135 addTypeAndDescendents(sdl, sd, worker.allStructures()); 6136 if (type.contains("#")) { 6137 tail = type.substring(type.indexOf("#") + 1); 6138 tail = tail.substring(tail.indexOf(".")); 6139 } 6140 } 6141 6142 for (StructureDefinition sdi : sdl) { 6143 String path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "."; 6144 if (name.equals("**")) { 6145 assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); 6146 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6147 if (ed.getPath().startsWith(path)) 6148 for (TypeRefComponent t : ed.getType()) { 6149 if (t.hasCode() && t.getCodeElement().hasValue()) { 6150 String tn = null; 6151 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { 6152 tn = sdi.getType() + "#" + ed.getPath(); 6153 } else { 6154 tn = t.getCode(); 6155 } 6156 if (t.getCode().equals("Resource")) { 6157 for (String rn : worker.getResourceNames()) { 6158 if (!result.hasType(worker, rn)) { 6159 getChildTypesByName(result.addType(rn), "**", result, expr); 6160 } 6161 } 6162 } else if (!result.hasType(worker, tn)) { 6163 getChildTypesByName(result.addType(tn), "**", result, expr); 6164 } 6165 } 6166 } 6167 } 6168 } else if (name.equals("*")) { 6169 assert (result.getCollectionStatus() == CollectionStatus.UNORDERED); 6170 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 6171 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 6172 for (TypeRefComponent t : ed.getType()) { 6173 if (Utilities.noString(t.getCode())) { // Element.id or Extension.url 6174 result.addType("System.string"); 6175 } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { 6176 result.addType(sdi.getType() + "#" + ed.getPath()); 6177 } else if (t.getCode().equals("Resource")) { 6178 result.addTypes(worker.getResourceNames()); 6179 } else { 6180 result.addType(t.getCode()); 6181 } 6182 } 6183 } 6184 } else { 6185 path = sdi.getSnapshot().getElement().get(0).getPath() + tail + "." + name; 6186 6187 ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr); 6188 if (ed != null) { 6189 if (!Utilities.noString(ed.getFixedType())) 6190 result.addType(ed.getFixedType()); 6191 else { 6192 for (TypeRefComponent t : ed.getDefinition().getType()) { 6193 if (Utilities.noString(t.getCode())) { 6194 if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") 6195 || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", 6196 "Extension.url")) { 6197 result.addType(TypeDetails.FP_NS, "string"); 6198 } 6199 break; // throw new PathEngineException("Illegal reference to primitive value attribute 6200 // @ "+path); 6201 } 6202 6203 ProfiledType pt = null; 6204 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) { 6205 pt = new ProfiledType(sdi.getUrl() + "#" + path); 6206 } else if (t.getCode().equals("Resource")) { 6207 result.addTypes(worker.getResourceNames()); 6208 } else { 6209 pt = new ProfiledType(t.getCode()); 6210 } 6211 if (pt != null) { 6212 if (t.hasProfile()) { 6213 pt.addProfiles(t.getProfile()); 6214 } 6215 if (ed.getDefinition().hasBinding()) { 6216 pt.addBinding(ed.getDefinition().getBinding()); 6217 } 6218 result.addType(pt); 6219 } 6220 } 6221 } 6222 } 6223 } 6224 } 6225 } 6226 } 6227 6228 private void addTypeAndDescendents(List<StructureDefinition> sdl, StructureDefinition dt, 6229 List<StructureDefinition> types) { 6230 sdl.add(dt); 6231 for (StructureDefinition sd : types) { 6232 if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) 6233 && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 6234 addTypeAndDescendents(sdl, sd, types); 6235 } 6236 } 6237 } 6238 6239 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 6240 if (name.equals("namespace")) { 6241 result.addType(TypeDetails.FP_String); 6242 } 6243 if (name.equals("name")) { 6244 result.addType(TypeDetails.FP_String); 6245 } 6246 } 6247 6248 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 6249 if (name.equals("namespace")) { 6250 result.addType(TypeDetails.FP_String); 6251 } 6252 if (name.equals("name")) { 6253 result.addType(TypeDetails.FP_String); 6254 } 6255 } 6256 6257 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, 6258 ExpressionNode expr) throws PathEngineException { 6259 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6260 if (ed.getPath().equals(path)) { 6261 if (ed.hasContentReference()) { 6262 return getElementDefinitionById(sd, ed.getContentReference()); 6263 } else { 6264 return new ElementDefinitionMatch(ed, null); 6265 } 6266 } 6267 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) 6268 && path.length() == ed.getPath().length() - 3) { 6269 return new ElementDefinitionMatch(ed, null); 6270 } 6271 if (allowTypedName && ed.getPath().endsWith("[x]") 6272 && path.startsWith(ed.getPath().substring(0, ed.getPath().length() - 3)) 6273 && path.length() > ed.getPath().length() - 3) { 6274 String s = Utilities.uncapitalize(path.substring(ed.getPath().length() - 3)); 6275 if (primitiveTypes.contains(s)) { 6276 return new ElementDefinitionMatch(ed, s); 6277 } else { 6278 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length() - 3)); 6279 } 6280 } 6281 if (ed.getPath().contains(".") && path.startsWith(ed.getPath() + ".") && (ed.getType().size() > 0) 6282 && !isAbstractType(ed.getType())) { 6283 // now we walk into the type. 6284 if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this 6285 throw new Error("Internal typing issue...."); 6286 } 6287 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, 6288 ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null)); 6289 if (nsd == null) { 6290 throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), 6291 "getElementDefinition"); 6292 } 6293 return getElementDefinition(nsd, nsd.getId() + path.substring(ed.getPath().length()), allowTypedName, expr); 6294 } 6295 if (ed.hasContentReference() && path.startsWith(ed.getPath() + ".")) { 6296 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 6297 return getElementDefinition(sd, m.definition.getPath() + path.substring(ed.getPath().length()), allowTypedName, 6298 expr); 6299 } 6300 } 6301 return null; 6302 } 6303 6304 private boolean isAbstractType(List<TypeRefComponent> list) { 6305 return list.size() != 1 ? true 6306 : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 6307 } 6308 6309 private boolean hasType(ElementDefinition ed, String s) { 6310 for (TypeRefComponent t : ed.getType()) { 6311 if (s.equalsIgnoreCase(t.getCode())) { 6312 return true; 6313 } 6314 } 6315 return false; 6316 } 6317 6318 private boolean hasDataType(ElementDefinition ed) { 6319 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") 6320 || ed.getType().get(0).getCode().equals("BackboneElement")); 6321 } 6322 6323 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 6324 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 6325 if (ref.equals("#" + ed.getId())) { 6326 return new ElementDefinitionMatch(ed, null); 6327 } 6328 } 6329 return null; 6330 } 6331 6332 public boolean hasLog() { 6333 return log != null && log.length() > 0; 6334 } 6335 6336 public String takeLog() { 6337 if (!hasLog()) { 6338 return ""; 6339 } 6340 String s = log.toString(); 6341 log = new StringBuilder(); 6342 return s; 6343 } 6344 6345 /** 6346 * given an element definition in a profile, what element contains the 6347 * differentiating fixed for the element, given the differentiating expresssion. 6348 * The expression is only allowed to use a subset of FHIRPath 6349 * 6350 * @param profile 6351 * @param element 6352 * @return 6353 * @throws PathEngineException 6354 * @throws DefinitionException 6355 */ 6356 public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, 6357 TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) 6358 throws DefinitionException { 6359 StructureDefinition sd = profile; 6360 TypedElementDefinition focus = null; 6361 boolean okToNotResolve = false; 6362 6363 if (expr.getKind() == Kind.Name) { 6364 if (element.getElement().hasSlicing()) { 6365 ElementDefinition slice = pickMandatorySlice(sd, element.getElement()); 6366 if (slice == null) { 6367 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, 6368 element.getElement().getId()); 6369 } 6370 element = new TypedElementDefinition(slice); 6371 } 6372 6373 if (expr.getName().equals("$this")) { 6374 focus = element; 6375 } else { 6376 List<ElementDefinition> childDefinitions; 6377 childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); 6378 // if that's empty, get the children of the type 6379 if (childDefinitions.isEmpty()) { 6380 6381 sd = fetchStructureByType(element, expr); 6382 if (sd == null) { 6383 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, 6384 element.getElement().getType().get(0).getProfile(), element.getElement().getId()); 6385 } 6386 childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 6387 } 6388 for (ElementDefinition t : childDefinitions) { 6389 if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an 6390 // exetnsion with a fixed value (type slicing) 6391 focus = new TypedElementDefinition(t); 6392 break; 6393 } 6394 } 6395 } 6396 } else if (expr.getKind() == Kind.Function) { 6397 if ("resolve".equals(expr.getName())) { 6398 if (element.getTypes().size() == 0) { 6399 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId()); 6400 } 6401 if (element.getTypes().size() > 1) { 6402 throw makeExceptionPlural(element.getTypes().size(), expr, 6403 I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId()); 6404 } 6405 if (!element.getTypes().get(0).hasTarget()) { 6406 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, 6407 element.getElement().getId(), element.getElement().getType().get(0).getCode() + ")"); 6408 } 6409 if (element.getTypes().get(0).getTargetProfile().size() > 1) { 6410 throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, 6411 I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId()); 6412 } 6413 sd = worker.fetchResource(StructureDefinition.class, 6414 element.getTypes().get(0).getTargetProfile().get(0).getValue()); 6415 if (sd == null) { 6416 throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, 6417 element.getTypes().get(0).getTargetProfile(), element.getElement().getId()); 6418 } 6419 focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep()); 6420 } else if ("extension".equals(expr.getName())) { 6421 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 6422 List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); 6423 for (ElementDefinition t : childDefinitions) { 6424 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 6425 StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() 6426 || t.getType().get(0).getProfile().isEmpty()) ? null 6427 : worker.fetchResource(StructureDefinition.class, 6428 t.getType().get(0).getProfile().get(0).getValue()); 6429 while (exsd != null 6430 && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { 6431 exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition()); 6432 } 6433 if (exsd != null && exsd.getUrl().equals(targetUrl)) { 6434 if (profileUtilities.getChildMap(sd, t).isEmpty()) { 6435 sd = exsd; 6436 } 6437 focus = new TypedElementDefinition(t); 6438 break; 6439 } 6440 } 6441 } 6442 if (focus == null) { 6443 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), 6444 targetUrl, element.getElement().getId(), sd.getUrl()); 6445 } 6446 } else if ("ofType".equals(expr.getName())) { 6447 if (!element.getElement().hasType()) { 6448 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId()); 6449 } 6450 List<String> atn = new ArrayList<>(); 6451 for (TypeRefComponent tr : element.getTypes()) { 6452 if (!tr.hasCode()) { 6453 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId()); 6454 } 6455 atn.add(tr.getCode()); 6456 } 6457 String stn = expr.getParameters().get(0).getName(); 6458 okToNotResolve = true; 6459 if ((atn.contains(stn))) { 6460 if (element.getTypes().size() > 1) { 6461 focus = new TypedElementDefinition(element.getSrc(), element.getElement(), stn); 6462 } else { 6463 focus = element; 6464 } 6465 } 6466 } else { 6467 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName()); 6468 } 6469 } else if (expr.getKind() == Kind.Group) { 6470 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString()); 6471 } else if (expr.getKind() == Kind.Constant) { 6472 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); 6473 } 6474 6475 if (focus == null) { 6476 if (okToNotResolve) { 6477 return null; 6478 } else { 6479 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), 6480 element.getElement().getId(), profile.getUrl()); 6481 } 6482 } else { 6483 // gdg 26-02-2022. If we're walking towards a resolve() and we're on a 6484 // reference, and we try to walk into the reference 6485 // then we don't do that. .resolve() is allowed on the Reference.reference, but 6486 // the target of the reference will be defined 6487 // on the Reference, not the reference.reference. 6488 ExpressionNode next = expr.getInner(); 6489 if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name 6490 && next.getName().equals("reference")) { 6491 next = next.getInner(); 6492 } 6493 if (next == null) { 6494 return focus; 6495 } else { 6496 return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences); 6497 } 6498 } 6499 } 6500 6501 private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) 6502 throws DefinitionException { 6503 List<ElementDefinition> list = profileUtilities.getSliceList(sd, element); 6504 for (ElementDefinition ed : list) { 6505 if (ed.getMin() > 0) { 6506 return ed; 6507 } 6508 } 6509 return null; 6510 } 6511 6512 private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) 6513 throws DefinitionException { 6514 if (ed.getTypes().size() == 0) { 6515 throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId()); 6516 } 6517 if (ed.getTypes().size() > 1) { 6518 throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, 6519 ed.getElement().getId()); 6520 } 6521 if (ed.getTypes().get(0).getProfile().size() > 1) { 6522 throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, 6523 I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId()); 6524 } 6525 if (ed.getTypes().get(0).hasProfile()) { 6526 return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue()); 6527 } else { 6528 return worker.fetchResource(StructureDefinition.class, 6529 ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null)); 6530 } 6531 } 6532 6533 private boolean tailMatches(ElementDefinition t, String d) { 6534 String tail = tailDot(t.getPath()); 6535 if (d.contains("[")) { 6536 return tail.startsWith(d.substring(0, d.indexOf('['))); 6537 } else if (tail.equals(d)) { 6538 return true; 6539 } else if (t.getType().size() == 1 && t.getType().get(0).getCode() != null && t.getPath() != null 6540 && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase())) { 6541 return tail.startsWith(d); 6542 } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) { 6543 return true; 6544 } 6545 return false; 6546 } 6547 6548 private String tailDot(String path) { 6549 return path.substring(path.lastIndexOf(".") + 1); 6550 } 6551 6552 private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException { 6553 if (items.size() == 0) { 6554 return Equality.Null; 6555 } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { 6556 return asBool(items.get(0), true); 6557 } else if (items.size() == 1) { 6558 return Equality.True; 6559 } else { 6560 throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items)); 6561 } 6562 } 6563 6564 private Equality asBoolFromInt(String s) { 6565 try { 6566 int i = Integer.parseInt(s); 6567 switch (i) { 6568 case 0: 6569 return Equality.False; 6570 case 1: 6571 return Equality.True; 6572 default: 6573 return Equality.Null; 6574 } 6575 } catch (Exception e) { 6576 return Equality.Null; 6577 } 6578 } 6579 6580 private Equality asBoolFromDec(String s) { 6581 try { 6582 BigDecimal d = new BigDecimal(s); 6583 if (d.compareTo(BigDecimal.ZERO) == 0) { 6584 return Equality.False; 6585 } else if (d.compareTo(BigDecimal.ONE) == 0) { 6586 return Equality.True; 6587 } else { 6588 return Equality.Null; 6589 } 6590 } catch (Exception e) { 6591 return Equality.Null; 6592 } 6593 } 6594 6595 private Equality asBool(Base item, boolean narrow) { 6596 if (item instanceof BooleanType) { 6597 return boolToTriState(((BooleanType) item).booleanValue()); 6598 } else if (item.isBooleanPrimitive()) { 6599 if (Utilities.existsInList(item.primitiveValue(), "true")) { 6600 return Equality.True; 6601 } else if (Utilities.existsInList(item.primitiveValue(), "false")) { 6602 return Equality.False; 6603 } else { 6604 return Equality.Null; 6605 } 6606 } else if (narrow) { 6607 return Equality.False; 6608 } else if (item instanceof IntegerType 6609 || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) { 6610 return asBoolFromInt(item.primitiveValue()); 6611 } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) { 6612 return asBoolFromDec(item.primitiveValue()); 6613 } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { 6614 if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) { 6615 return Equality.True; 6616 } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) { 6617 return Equality.False; 6618 } else if (Utilities.isInteger(item.primitiveValue())) { 6619 return asBoolFromInt(item.primitiveValue()); 6620 } else if (Utilities.isDecimal(item.primitiveValue(), true)) { 6621 return asBoolFromDec(item.primitiveValue()); 6622 } else { 6623 return Equality.Null; 6624 } 6625 } 6626 return Equality.Null; 6627 } 6628 6629 private Equality boolToTriState(boolean b) { 6630 return b ? Equality.True : Equality.False; 6631 } 6632 6633 public ValidationOptions getTerminologyServiceOptions() { 6634 return terminologyServiceOptions; 6635 } 6636 6637 public IWorkerContext getWorker() { 6638 return worker; 6639 } 6640 6641 public boolean isAllowPolymorphicNames() { 6642 return allowPolymorphicNames; 6643 } 6644 6645 public void setAllowPolymorphicNames(boolean allowPolymorphicNames) { 6646 this.allowPolymorphicNames = allowPolymorphicNames; 6647 } 6648 6649 public boolean isLiquidMode() { 6650 return liquidMode; 6651 } 6652 6653 public void setLiquidMode(boolean liquidMode) { 6654 this.liquidMode = liquidMode; 6655 } 6656 6657}