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