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