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