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