001package org.hl7.fhir.r4.utils; 002 003import java.math.BigDecimal; 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.EnumSet; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012import java.util.TimeZone; 013 014import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 015import org.apache.commons.lang3.NotImplementedException; 016import org.fhir.ucum.Decimal; 017import org.fhir.ucum.Pair; 018import org.fhir.ucum.UcumException; 019import org.hl7.fhir.exceptions.DefinitionException; 020import org.hl7.fhir.exceptions.FHIRException; 021import org.hl7.fhir.exceptions.PathEngineException; 022import org.hl7.fhir.r4.conformance.ProfileUtilities; 023import org.hl7.fhir.r4.context.IWorkerContext; 024import org.hl7.fhir.r4.model.Base; 025import org.hl7.fhir.r4.model.BaseDateTimeType; 026import org.hl7.fhir.r4.model.BooleanType; 027import org.hl7.fhir.r4.model.DateTimeType; 028import org.hl7.fhir.r4.model.DateType; 029import org.hl7.fhir.r4.model.DecimalType; 030import org.hl7.fhir.r4.model.Element; 031import org.hl7.fhir.r4.model.ElementDefinition; 032import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 033import org.hl7.fhir.r4.model.ExpressionNode; 034import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus; 035import org.hl7.fhir.r4.model.ExpressionNode.Function; 036import org.hl7.fhir.r4.model.ExpressionNode.Kind; 037import org.hl7.fhir.r4.model.ExpressionNode.Operation; 038import org.hl7.fhir.r4.model.ExpressionNode.SourceLocation; 039import org.hl7.fhir.r4.model.IntegerType; 040import org.hl7.fhir.r4.model.Property; 041import org.hl7.fhir.r4.model.Quantity; 042import org.hl7.fhir.r4.model.Resource; 043import org.hl7.fhir.r4.model.StringType; 044import org.hl7.fhir.r4.model.StructureDefinition; 045import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 046import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 047import org.hl7.fhir.r4.model.TimeType; 048import org.hl7.fhir.r4.model.TypeDetails; 049import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; 050import org.hl7.fhir.r4.model.ValueSet; 051import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; 052import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; 053import org.hl7.fhir.utilities.Utilities; 054 055/*- 056 * #%L 057 * org.hl7.fhir.r4 058 * %% 059 * Copyright (C) 2014 - 2019 Health Level 7 060 * %% 061 * Licensed under the Apache License, Version 2.0 (the "License"); 062 * you may not use this file except in compliance with the License. 063 * You may obtain a copy of the License at 064 * 065 * http://www.apache.org/licenses/LICENSE-2.0 066 * 067 * Unless required by applicable law or agreed to in writing, software 068 * distributed under the License is distributed on an "AS IS" BASIS, 069 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 070 * See the License for the specific language governing permissions and 071 * limitations under the License. 072 * #L% 073 */ 074 075//import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 076import ca.uhn.fhir.util.ElementUtil; 077 078/** 079 * 080 * @author Grahame Grieve 081 * 082 */ 083public class FHIRPathEngine { 084 private enum Equality { Null, True, False } 085 086 private class FHIRConstant extends Base { 087 088 private static final long serialVersionUID = -8933773658248269439L; 089 private String value; 090 091 public FHIRConstant(String value) { 092 this.value = value; 093 } 094 095 @Override 096 public String fhirType() { 097 return "%constant"; 098 } 099 100 @Override 101 protected void listChildren(List<Property> result) { 102 } 103 104 @Override 105 public String getIdBase() { 106 return null; 107 } 108 109 @Override 110 public void setIdBase(String value) { 111 } 112 113 public String getValue() { 114 return value; 115 } 116 } 117 118 private class ClassTypeInfo extends Base { 119 private static final long serialVersionUID = 4909223114071029317L; 120 private Base instance; 121 122 public ClassTypeInfo(Base instance) { 123 super(); 124 this.instance = instance; 125 } 126 127 @Override 128 public String fhirType() { 129 return "ClassInfo"; 130 } 131 132 @Override 133 protected void listChildren(List<Property> result) { 134 } 135 136 @Override 137 public String getIdBase() { 138 return null; 139 } 140 141 @Override 142 public void setIdBase(String value) { 143 } 144 public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException { 145 if (name.equals("name")) 146 return new Base[]{new StringType(getName())}; 147 else if (name.equals("namespace")) 148 return new Base[]{new StringType(getNamespace())}; 149 else 150 return super.getProperty(hash, name, checkValid); 151 } 152 153 private String getNamespace() { 154 if ((instance instanceof Resource)) 155 return "FHIR"; 156 else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) 157 return "System"; 158 else 159 return "FHIR"; 160 } 161 162 private String getName() { 163 if ((instance instanceof Resource)) 164 return instance.fhirType(); 165 else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) 166 return Utilities.capitalize(instance.fhirType()); 167 else 168 return instance.fhirType(); 169 } 170 } 171 172 private IWorkerContext worker; 173 private IEvaluationContext hostServices; 174 private StringBuilder log = new StringBuilder(); 175 private Set<String> primitiveTypes = new HashSet<String>(); 176 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 177 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 178 179 // if the fhir path expressions are allowed to use constants beyond those defined in the specification 180 // the application can implement them by providing a constant resolver 181 public interface IEvaluationContext { 182 public class FunctionDetails { 183 private String description; 184 private int minParameters; 185 private int maxParameters; 186 public FunctionDetails(String description, int minParameters, int maxParameters) { 187 super(); 188 this.description = description; 189 this.minParameters = minParameters; 190 this.maxParameters = maxParameters; 191 } 192 public String getDescription() { 193 return description; 194 } 195 public int getMinParameters() { 196 return minParameters; 197 } 198 public int getMaxParameters() { 199 return maxParameters; 200 } 201 202 } 203 204 /** 205 * A constant reference - e.g. a reference to a name that must be resolved in context. 206 * The % will be removed from the constant name before this is invoked. 207 * 208 * This will also be called if the host invokes the FluentPath engine with a context of null 209 * 210 * @param appContext - content passed into the fluent path engine 211 * @param name - name reference to resolve 212 * @param beforeContext - whether this is being called before the name is resolved locally, or not 213 * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired) 214 */ 215 public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException; 216 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException; 217 218 /** 219 * when the .log() function is called 220 * 221 * @param argument 222 * @param focus 223 * @return 224 */ 225 public boolean log(String argument, List<Base> focus); 226 227 // extensibility for functions 228 /** 229 * 230 * @param functionName 231 * @return null if the function is not known 232 */ 233 public FunctionDetails resolveFunction(String functionName); 234 235 /** 236 * Check the function parameters, and throw an error if they are incorrect, or return the type for the function 237 * @param functionName 238 * @param parameters 239 * @return 240 */ 241 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException; 242 243 /** 244 * @param appContext 245 * @param functionName 246 * @param parameters 247 * @return 248 */ 249 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters); 250 251 /** 252 * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null 253 * @param url 254 * @return 255 * @throws FHIRException 256 */ 257 public Base resolveReference(Object appContext, String url) throws FHIRException; 258 259 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException; 260 } 261 262 263 /** 264 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 265 */ 266 public FHIRPathEngine(IWorkerContext worker) { 267 super(); 268 this.worker = worker; 269 for (StructureDefinition sd : worker.allStructures()) { 270 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) 271 allTypes.put(sd.getName(), sd); 272 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 273 primitiveTypes.add(sd.getName()); 274 } 275 } 276 } 277 278 279 // --- 3 methods to override in children ------------------------------------------------------- 280 // if you don't override, it falls through to the using the base reference implementation 281 // HAPI overrides to these to support extending the base model 282 283 public IEvaluationContext getHostServices() { 284 return hostServices; 285 } 286 287 288 public void setHostServices(IEvaluationContext constantResolver) { 289 this.hostServices = constantResolver; 290 } 291 292 293 /** 294 * Given an item, return all the children that conform to the pattern described in name 295 * 296 * Possible patterns: 297 * - a simple name (which may be the base of a name with [] e.g. value[x]) 298 * - a name with a type replacement e.g. valueCodeableConcept 299 * - * which means all children 300 * - ** which means all descendants 301 * 302 * @param item 303 * @param name 304 * @param result 305 * @throws FHIRException 306 */ 307 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 308 Base[] list = item.listChildrenByName(name, false); 309 if (list != null) 310 for (Base v : list) 311 if (v != null) 312 result.add(v); 313 } 314 315 316 public boolean isLegacyMode() { 317 return legacyMode; 318 } 319 320 321 public void setLegacyMode(boolean legacyMode) { 322 this.legacyMode = legacyMode; 323 } 324 325 326 // --- public API ------------------------------------------------------- 327 /** 328 * Parse a path for later use using execute 329 * 330 * @param path 331 * @return 332 * @throws PathEngineException 333 * @throws Exception 334 */ 335 public ExpressionNode parse(String path) throws FHIRLexerException { 336 return parse(path, null); 337 } 338 339 public ExpressionNode parse(String path, String name) throws FHIRLexerException { 340 FHIRLexer lexer = new FHIRLexer(path, name); 341 if (lexer.done()) 342 throw lexer.error("Path cannot be empty"); 343 ExpressionNode result = parseExpression(lexer, true); 344 if (!lexer.done()) 345 throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); 346 result.check(); 347 return result; 348 } 349 350 public static class ExpressionNodeWithOffset { 351 private int offset; 352 private ExpressionNode node; 353 public ExpressionNodeWithOffset(int offset, ExpressionNode node) { 354 super(); 355 this.offset = offset; 356 this.node = node; 357 } 358 public int getOffset() { 359 return offset; 360 } 361 public ExpressionNode getNode() { 362 return node; 363 } 364 365 } 366 /** 367 * Parse a path for later use using execute 368 * 369 * @param path 370 * @return 371 * @throws PathEngineException 372 * @throws Exception 373 */ 374 public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException { 375 FHIRLexer lexer = new FHIRLexer(path, i); 376 if (lexer.done()) 377 throw lexer.error("Path cannot be empty"); 378 ExpressionNode result = parseExpression(lexer, true); 379 result.check(); 380 return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result); 381 } 382 383 /** 384 * Parse a path that is part of some other syntax 385 * 386 * @return 387 * @throws PathEngineException 388 * @throws Exception 389 */ 390 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 391 ExpressionNode result = parseExpression(lexer, true); 392 result.check(); 393 return result; 394 } 395 396 /** 397 * check that paths referred to in the ExpressionNode are valid 398 * 399 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 400 * 401 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 402 * 403 * @param context - the logical type against which this path is applied 404 * @throws DefinitionException 405 * @throws PathEngineException 406 * @if the path is not valid 407 */ 408 public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 409 // if context is a path that refers to a type, do that conversion now 410 TypeDetails types; 411 if (context == null) { 412 types = null; // this is a special case; the first path reference will have to resolve to something in the context 413 } else if (!context.contains(".")) { 414 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context); 415 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 416 } else { 417 String ctxt = context.substring(0, context.indexOf('.')); 418 if (Utilities.isAbsoluteUrl(resourceType)) { 419 ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt; 420 } 421 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); 422 if (sd == null) 423 throw new PathEngineException("Unknown context "+context); 424 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 425 if (ed == null) 426 throw new PathEngineException("Unknown context element "+context); 427 if (ed.fixedType != null) 428 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 429 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 430 types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context); 431 else { 432 types = new TypeDetails(CollectionStatus.SINGLETON); 433 for (TypeRefComponent t : ed.getDefinition().getType()) 434 types.addType(t.getCode()); 435 } 436 } 437 438 return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true); 439 } 440 441 public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 442 // if context is a path that refers to a type, do that conversion now 443 TypeDetails types; 444 if (!context.contains(".")) { 445 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 446 } else { 447 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 448 if (ed == null) 449 throw new PathEngineException("Unknown context element "+context); 450 if (ed.fixedType != null) 451 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 452 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 453 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); 454 else { 455 types = new TypeDetails(CollectionStatus.SINGLETON); 456 for (TypeRefComponent t : ed.getDefinition().getType()) 457 types.addType(t.getCode()); 458 } 459 } 460 461 return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true); 462 } 463 464 public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 465 // if context is a path that refers to a type, do that conversion now 466 TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context 467 return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true); 468 } 469 470 public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 471 return check(appContext, resourceType, context, parse(expr)); 472 } 473 474 private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) { 475 String dateLeftString = theL.primitiveValue(); 476 DateTimeType dateLeft = new DateTimeType(dateLeftString); 477 478 String dateRightString = theR.primitiveValue(); 479 DateTimeType dateRight = new DateTimeType(dateRightString); 480 481 if (theEquivalenceTest) { 482 return dateLeft.equalsUsingFhirPathRules(dateRight) == Boolean.TRUE ? 0 : 1; 483 } 484 485 if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 486 dateLeft.setTimeZoneZulu(true); 487 } 488 if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) { 489 dateRight.setTimeZoneZulu(true); 490 } 491 492 dateLeftString = dateLeft.getValueAsString(); 493 dateRightString = dateRight.getValueAsString(); 494 495 return dateLeftString.compareTo(dateRightString); 496 } 497 498 /** 499 * evaluate a path and return the matching elements 500 * 501 * @param base - the object against which the path is being evaluated 502 * @param ExpressionNode - the parsed ExpressionNode statement to use 503 * @return 504 * @throws FHIRException 505 * @ 506 */ 507 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { 508 List<Base> list = new ArrayList<Base>(); 509 if (base != null) 510 list.add(base); 511 log = new StringBuilder(); 512 return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); 513 } 514 515 /** 516 * evaluate a path and return the matching elements 517 * 518 * @param base - the object against which the path is being evaluated 519 * @param path - the FHIR Path statement to use 520 * @return 521 * @throws FHIRException 522 * @ 523 */ 524 public List<Base> evaluate(Base base, String path) throws FHIRException { 525 ExpressionNode exp = parse(path); 526 List<Base> list = new ArrayList<Base>(); 527 if (base != null) 528 list.add(base); 529 log = new StringBuilder(); 530 return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true); 531 } 532 533 /** 534 * evaluate a path and return the matching elements 535 * 536 * @param base - the object against which the path is being evaluated 537 * @param ExpressionNode - the parsed ExpressionNode statement to use 538 * @return 539 * @throws FHIRException 540 * @ 541 */ 542 public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 543 List<Base> list = new ArrayList<Base>(); 544 if (base != null) 545 list.add(base); 546 log = new StringBuilder(); 547 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 548 } 549 550 /** 551 * evaluate a path and return the matching elements 552 * 553 * @param base - the object against which the path is being evaluated 554 * @param ExpressionNode - the parsed ExpressionNode statement to use 555 * @return 556 * @throws FHIRException 557 * @ 558 */ 559 public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 560 List<Base> list = new ArrayList<Base>(); 561 if (base != null) 562 list.add(base); 563 log = new StringBuilder(); 564 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 565 } 566 567 /** 568 * evaluate a path and return the matching elements 569 * 570 * @param base - the object against which the path is being evaluated 571 * @param path - the FHIR Path statement to use 572 * @return 573 * @throws FHIRException 574 * @ 575 */ 576 public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException { 577 ExpressionNode exp = parse(path); 578 List<Base> list = new ArrayList<Base>(); 579 if (base != null) 580 list.add(base); 581 log = new StringBuilder(); 582 return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true); 583 } 584 585 /** 586 * evaluate a path and return true or false (e.g. for an invariant) 587 * 588 * @param base - the object against which the path is being evaluated 589 * @param path - the FHIR Path statement to use 590 * @return 591 * @throws FHIRException 592 * @ 593 */ 594 public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException { 595 return convertToBoolean(evaluate(null, resource, base, path)); 596 } 597 598 /** 599 * evaluate a path and return true or false (e.g. for an invariant) 600 * 601 * @param base - the object against which the path is being evaluated 602 * @return 603 * @throws FHIRException 604 * @ 605 */ 606 public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException { 607 return convertToBoolean(evaluate(null, resource, base, node)); 608 } 609 610 /** 611 * evaluate a path and return true or false (e.g. for an invariant) 612 * 613 * @param appInfo - application context 614 * @param base - the object against which the path is being evaluated 615 * @return 616 * @throws FHIRException 617 * @ 618 */ 619 public boolean evaluateToBoolean(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException { 620 return convertToBoolean(evaluate(appInfo, resource, base, node)); 621 } 622 623 /** 624 * evaluate a path and return true or false (e.g. for an invariant) 625 * 626 * @param base - the object against which the path is being evaluated 627 * @return 628 * @throws FHIRException 629 * @ 630 */ 631 public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException { 632 return convertToBoolean(evaluate(null, resource, base, node)); 633 } 634 635 /** 636 * evaluate a path and a string containing the outcome (for display) 637 * 638 * @param base - the object against which the path is being evaluated 639 * @param path - the FHIR Path statement to use 640 * @return 641 * @throws FHIRException 642 * @ 643 */ 644 public String evaluateToString(Base base, String path) throws FHIRException { 645 return convertToString(evaluate(base, path)); 646 } 647 648 public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException { 649 return convertToString(evaluate(appInfo, resource, base, node)); 650 } 651 652 /** 653 * worker routine for converting a set of objects to a string representation 654 * 655 * @param items - result from @evaluate 656 * @return 657 */ 658 public String convertToString(List<Base> items) { 659 StringBuilder b = new StringBuilder(); 660 boolean first = true; 661 for (Base item : items) { 662 if (first) 663 first = false; 664 else 665 b.append(','); 666 667 b.append(convertToString(item)); 668 } 669 return b.toString(); 670 } 671 672 public String convertToString(Base item) { 673 if (item.isPrimitive()) 674 return item.primitiveValue(); 675 else if (item instanceof Quantity) { 676 Quantity q = (Quantity) item; 677 if (q.getSystem().equals("http://unitsofmeasure.org")) { 678 String u = "'"+q.getCode()+"'"; 679 return q.getValue().toPlainString()+" "+u; 680 } 681 else 682 return item.toString(); 683 } else 684 return item.toString(); 685 } 686 687 /** 688 * worker routine for converting a set of objects to a boolean representation (for invariants) 689 * 690 * @param items - result from @evaluate 691 * @return 692 */ 693 public boolean convertToBoolean(List<Base> items) { 694 if (items == null) 695 return false; 696 else if (items.size() == 1 && items.get(0) instanceof BooleanType) 697 return ((BooleanType) items.get(0)).getValue(); 698 else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model 699 return Boolean.valueOf(items.get(0).primitiveValue()); 700 else 701 return items.size() > 0; 702 } 703 704 705 private void log(String name, List<Base> contents) { 706 if (hostServices == null || !hostServices.log(name, contents)) { 707 if (log.length() > 0) 708 log.append("; "); 709 log.append(name); 710 log.append(": "); 711 boolean first = true; 712 for (Base b : contents) { 713 if (first) 714 first = false; 715 else 716 log.append(","); 717 log.append(convertToString(b)); 718 } 719 } 720 } 721 722 public String forLog() { 723 if (log.length() > 0) 724 return " ("+log.toString()+")"; 725 else 726 return ""; 727 } 728 729 private class ExecutionContext { 730 private Object appInfo; 731 private Base resource; 732 private Base context; 733 private Base thisItem; 734 private List<Base> total; 735 private Map<String, Base> aliases; 736 737 public ExecutionContext(Object appInfo, Base resource, Base context, Map<String, Base> aliases, Base thisItem) { 738 this.appInfo = appInfo; 739 this.context = context; 740 this.resource = resource; 741 this.aliases = aliases; 742 this.thisItem = thisItem; 743 } 744 public Base getResource() { 745 return resource; 746 } 747 public Base getThisItem() { 748 return thisItem; 749 } 750 public List<Base> getTotal() { 751 return total; 752 } 753 public void addAlias(String name, List<Base> focus) throws FHIRException { 754 if (aliases == null) 755 aliases = new HashMap<String, Base>(); 756 else 757 aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 758 if (focus.size() > 1) 759 throw new FHIRException("Attempt to alias a collection, not a singleton"); 760 aliases.put(name, focus.size() == 0 ? null : focus.get(0)); 761 } 762 public Base getAlias(String name) { 763 return aliases == null ? null : aliases.get(name); 764 } 765 } 766 767 private class ExecutionTypeContext { 768 private Object appInfo; 769 private String resource; 770 private TypeDetails context; 771 private TypeDetails thisItem; 772 private TypeDetails total; 773 774 775 public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) { 776 super(); 777 this.appInfo = appInfo; 778 this.resource = resource; 779 this.context = context; 780 this.thisItem = thisItem; 781 782 } 783 public String getResource() { 784 return resource; 785 } 786 public TypeDetails getThisItem() { 787 return thisItem; 788 } 789 790 791 } 792 793 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 794 ExpressionNode result = new ExpressionNode(lexer.nextId()); 795 ExpressionNode wrapper = null; 796 SourceLocation c = lexer.getCurrentStartLocation(); 797 result.setStart(lexer.getCurrentLocation()); 798 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 799 // so we back correct for both +/- and as part of a numeric constant below. 800 801 // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true. 802 // so we back correct for both +/- and as part of a numeric constant below. 803 if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) { 804 wrapper = new ExpressionNode(lexer.nextId()); 805 wrapper.setKind(Kind.Unary); 806 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take())); 807 wrapper.setProximal(proximal); 808 } 809 810 if (lexer.isConstant()) { 811 boolean isString = lexer.isStringConstant(); 812 if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) { 813 // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations 814 wrapper = new ExpressionNode(lexer.nextId()); 815 wrapper.setKind(Kind.Unary); 816 wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1))); 817 wrapper.setProximal(proximal); 818 lexer.setCurrent(lexer.getCurrent().substring(1)); 819 } 820 result.setConstant(processConstant(lexer)); 821 result.setKind(Kind.Constant); 822 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"))) { 823 // it's a quantity 824 String ucum = null; 825 if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) { 826 String s = lexer.take(); 827 if (s.equals("year") || s.equals("years")) 828 ucum = "a"; 829 else if (s.equals("month") || s.equals("months")) 830 ucum = "mo"; 831 else if (s.equals("week") || s.equals("weeks")) 832 ucum = "wk"; 833 else if (s.equals("day") || s.equals("days")) 834 ucum = "d"; 835 else if (s.equals("hour") || s.equals("hours")) 836 ucum = "h"; 837 else if (s.equals("minute") || s.equals("minutes")) 838 ucum = "min"; 839 else if (s.equals("second") || s.equals("seconds")) 840 ucum = "s"; 841 else // (s.equals("millisecond") || s.equals("milliseconds")) 842 ucum = "ms"; 843 } else 844 ucum = lexer.readConstant("units"); 845 result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum)); 846 } 847 result.setEnd(lexer.getCurrentLocation()); 848 } else if ("(".equals(lexer.getCurrent())) { 849 lexer.next(); 850 result.setKind(Kind.Group); 851 result.setGroup(parseExpression(lexer, true)); 852 if (!")".equals(lexer.getCurrent())) 853 throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); 854 result.setEnd(lexer.getCurrentLocation()); 855 lexer.next(); 856 } else { 857 if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) 858 throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); 859 if (lexer.isFixedName()) 860 result.setName(lexer.readFixedName("Path Name")); 861 else 862 result.setName(lexer.take()); 863 result.setEnd(lexer.getCurrentLocation()); 864 if (!result.checkName()) 865 throw lexer.error("Found "+result.getName()+" expecting a valid token name"); 866 if ("(".equals(lexer.getCurrent())) { 867 Function f = Function.fromCode(result.getName()); 868 FunctionDetails details = null; 869 if (f == null) { 870 if (hostServices != null) 871 details = hostServices.resolveFunction(result.getName()); 872 if (details == null) 873 throw lexer.error("The name "+result.getName()+" is not a valid function name"); 874 f = Function.Custom; 875 } 876 result.setKind(Kind.Function); 877 result.setFunction(f); 878 lexer.next(); 879 while (!")".equals(lexer.getCurrent())) { 880 result.getParameters().add(parseExpression(lexer, true)); 881 if (",".equals(lexer.getCurrent())) 882 lexer.next(); 883 else if (!")".equals(lexer.getCurrent())) 884 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); 885 } 886 result.setEnd(lexer.getCurrentLocation()); 887 lexer.next(); 888 checkParameters(lexer, c, result, details); 889 } else 890 result.setKind(Kind.Name); 891 } 892 ExpressionNode focus = result; 893 if ("[".equals(lexer.getCurrent())) { 894 lexer.next(); 895 ExpressionNode item = new ExpressionNode(lexer.nextId()); 896 item.setKind(Kind.Function); 897 item.setFunction(ExpressionNode.Function.Item); 898 item.getParameters().add(parseExpression(lexer, true)); 899 if (!lexer.getCurrent().equals("]")) 900 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); 901 lexer.next(); 902 result.setInner(item); 903 focus = item; 904 } 905 if (".".equals(lexer.getCurrent())) { 906 lexer.next(); 907 focus.setInner(parseExpression(lexer, false)); 908 } 909 result.setProximal(proximal); 910 if (proximal) { 911 while (lexer.isOp()) { 912 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 913 focus.setOpStart(lexer.getCurrentStartLocation()); 914 focus.setOpEnd(lexer.getCurrentLocation()); 915 lexer.next(); 916 focus.setOpNext(parseExpression(lexer, false)); 917 focus = focus.getOpNext(); 918 } 919 result = organisePrecedence(lexer, result); 920 } 921 if (wrapper != null) { 922 wrapper.setOpNext(result); 923 result.setProximal(false); 924 result = wrapper; 925 } 926 return result; 927 } 928 929 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 930 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 931 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 932 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 933 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 934 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); 935 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 936 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 937 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 938 // last: implies 939 return node; 940 } 941 942 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 943 // work : boolean; 944 // focus, node, group : ExpressionNode; 945 946 assert(start.isProximal()); 947 948 // is there anything to do? 949 boolean work = false; 950 ExpressionNode focus = start.getOpNext(); 951 if (ops.contains(start.getOperation())) { 952 while (focus != null && focus.getOperation() != null) { 953 work = work || !ops.contains(focus.getOperation()); 954 focus = focus.getOpNext(); 955 } 956 } else { 957 while (focus != null && focus.getOperation() != null) { 958 work = work || ops.contains(focus.getOperation()); 959 focus = focus.getOpNext(); 960 } 961 } 962 if (!work) 963 return start; 964 965 // entry point: tricky 966 ExpressionNode group; 967 if (ops.contains(start.getOperation())) { 968 group = newGroup(lexer, start); 969 group.setProximal(true); 970 focus = start; 971 start = group; 972 } else { 973 ExpressionNode node = start; 974 975 focus = node.getOpNext(); 976 while (!ops.contains(focus.getOperation())) { 977 node = focus; 978 focus = focus.getOpNext(); 979 } 980 group = newGroup(lexer, focus); 981 node.setOpNext(group); 982 } 983 984 // now, at this point: 985 // group is the group we are adding to, it already has a .group property filled out. 986 // focus points at the group.group 987 do { 988 // run until we find the end of the sequence 989 while (ops.contains(focus.getOperation())) 990 focus = focus.getOpNext(); 991 if (focus.getOperation() != null) { 992 group.setOperation(focus.getOperation()); 993 group.setOpNext(focus.getOpNext()); 994 focus.setOperation(null); 995 focus.setOpNext(null); 996 // now look for another sequence, and start it 997 ExpressionNode node = group; 998 focus = group.getOpNext(); 999 if (focus != null) { 1000 while (focus != null && !ops.contains(focus.getOperation())) { 1001 node = focus; 1002 focus = focus.getOpNext(); 1003 } 1004 if (focus != null) { // && (focus.Operation in Ops) - must be true 1005 group = newGroup(lexer, focus); 1006 node.setOpNext(group); 1007 } 1008 } 1009 } 1010 } 1011 while (focus != null && focus.getOperation() != null); 1012 return start; 1013 } 1014 1015 1016 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 1017 ExpressionNode result = new ExpressionNode(lexer.nextId()); 1018 result.setKind(Kind.Group); 1019 result.setGroup(next); 1020 result.getGroup().setProximal(true); 1021 return result; 1022 } 1023 1024 private Base processConstant(FHIRLexer lexer) throws FHIRLexerException { 1025 if (lexer.isStringConstant()) { 1026 return new StringType(processConstantString(lexer.take(), lexer)).noExtensions(); 1027 } else if (Utilities.isInteger(lexer.getCurrent())) { 1028 return new IntegerType(lexer.take()).noExtensions(); 1029 } else if (Utilities.isDecimal(lexer.getCurrent())) { 1030 return new DecimalType(lexer.take()).noExtensions(); 1031 } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) { 1032 return new BooleanType(lexer.take()).noExtensions(); 1033 } else if (lexer.getCurrent().equals("{}")) { 1034 lexer.take(); 1035 return null; 1036 } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) { 1037 return new FHIRConstant(lexer.take()); 1038 } else 1039 throw lexer.error("Invalid Constant "+lexer.getCurrent()); 1040 } 1041 1042 // procedure CheckParamCount(c : integer); 1043 // begin 1044 // if exp.Parameters.Count <> c then 1045 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 1046 // end; 1047 1048 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 1049 if (exp.getParameters().size() != count) 1050 throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString()); 1051 return true; 1052 } 1053 1054 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 1055 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) 1056 throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString()); 1057 return true; 1058 } 1059 1060 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 1061 switch (exp.getFunction()) { 1062 case Empty: return checkParamCount(lexer, location, exp, 0); 1063 case Not: return checkParamCount(lexer, location, exp, 0); 1064 case Exists: return checkParamCount(lexer, location, exp, 0); 1065 case SubsetOf: return checkParamCount(lexer, location, exp, 1); 1066 case SupersetOf: return checkParamCount(lexer, location, exp, 1); 1067 case IsDistinct: return checkParamCount(lexer, location, exp, 0); 1068 case Distinct: return checkParamCount(lexer, location, exp, 0); 1069 case Count: return checkParamCount(lexer, location, exp, 0); 1070 case Where: return checkParamCount(lexer, location, exp, 1); 1071 case Select: return checkParamCount(lexer, location, exp, 1); 1072 case All: return checkParamCount(lexer, location, exp, 0, 1); 1073 case Repeat: return checkParamCount(lexer, location, exp, 1); 1074 case Aggregate: return checkParamCount(lexer, location, exp, 1, 2); 1075 case Item: return checkParamCount(lexer, location, exp, 1); 1076 case As: return checkParamCount(lexer, location, exp, 1); 1077 case OfType: return checkParamCount(lexer, location, exp, 1); 1078 case Type: return checkParamCount(lexer, location, exp, 0); 1079 case Is: return checkParamCount(lexer, location, exp, 1); 1080 case Single: return checkParamCount(lexer, location, exp, 0); 1081 case First: return checkParamCount(lexer, location, exp, 0); 1082 case Last: return checkParamCount(lexer, location, exp, 0); 1083 case Tail: return checkParamCount(lexer, location, exp, 0); 1084 case Skip: return checkParamCount(lexer, location, exp, 1); 1085 case Take: return checkParamCount(lexer, location, exp, 1); 1086 case Union: return checkParamCount(lexer, location, exp, 1); 1087 case Combine: return checkParamCount(lexer, location, exp, 1); 1088 case Intersect: return checkParamCount(lexer, location, exp, 1); 1089 case Exclude: return checkParamCount(lexer, location, exp, 1); 1090 case Iif: return checkParamCount(lexer, location, exp, 2,3); 1091 case Lower: return checkParamCount(lexer, location, exp, 0); 1092 case Upper: return checkParamCount(lexer, location, exp, 0); 1093 case ToChars: return checkParamCount(lexer, location, exp, 0); 1094 case Substring: return checkParamCount(lexer, location, exp, 1, 2); 1095 case StartsWith: return checkParamCount(lexer, location, exp, 1); 1096 case EndsWith: return checkParamCount(lexer, location, exp, 1); 1097 case Matches: return checkParamCount(lexer, location, exp, 1); 1098 case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); 1099 case Contains: return checkParamCount(lexer, location, exp, 1); 1100 case Replace: return checkParamCount(lexer, location, exp, 2); 1101 case Length: return checkParamCount(lexer, location, exp, 0); 1102 case Children: return checkParamCount(lexer, location, exp, 0); 1103 case Descendants: return checkParamCount(lexer, location, exp, 0); 1104 case MemberOf: return checkParamCount(lexer, location, exp, 1); 1105 case Trace: return checkParamCount(lexer, location, exp, 1, 2); 1106 case Today: return checkParamCount(lexer, location, exp, 0); 1107 case Now: return checkParamCount(lexer, location, exp, 0); 1108 case Resolve: return checkParamCount(lexer, location, exp, 0); 1109 case Extension: return checkParamCount(lexer, location, exp, 1); 1110 case AllFalse: return checkParamCount(lexer, location, exp, 0); 1111 case AnyFalse: return checkParamCount(lexer, location, exp, 0); 1112 case AllTrue: return checkParamCount(lexer, location, exp, 0); 1113 case AnyTrue: return checkParamCount(lexer, location, exp, 0); 1114 case HasValue: return checkParamCount(lexer, location, exp, 0); 1115 case Alias: return checkParamCount(lexer, location, exp, 1); 1116 case AliasAs: return checkParamCount(lexer, location, exp, 1); 1117 case HtmlChecks: return checkParamCount(lexer, location, exp, 0); 1118 case ToInteger: return checkParamCount(lexer, location, exp, 0); 1119 case ToDecimal: return checkParamCount(lexer, location, exp, 0); 1120 case ToString: return checkParamCount(lexer, location, exp, 0); 1121 case ToQuantity: return checkParamCount(lexer, location, exp, 0); 1122 case ToBoolean: return checkParamCount(lexer, location, exp, 0); 1123 case ToDateTime: return checkParamCount(lexer, location, exp, 0); 1124 case ToTime: return checkParamCount(lexer, location, exp, 0); 1125 case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0); 1126 case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0); 1127 case ConvertsToString: return checkParamCount(lexer, location, exp, 0); 1128 case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0); 1129 case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0); 1130 case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0); 1131 case ConvertsToTime: return checkParamCount(lexer, location, exp, 0); 1132 case ConformsTo: return checkParamCount(lexer, location, exp, 1); 1133 case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 1134 } 1135 return false; 1136 } 1137 1138 private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException { 1139// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 1140 List<Base> work = new ArrayList<Base>(); 1141 switch (exp.getKind()) { 1142 case Unary: 1143 work.add(new IntegerType(0)); 1144 break; 1145 case Name: 1146 if (atEntry && exp.getName().equals("$this")) 1147 work.add(context.getThisItem()); 1148 else if (atEntry && exp.getName().equals("$total")) 1149 work.addAll(context.getTotal()); 1150 else 1151 for (Base item : focus) { 1152 List<Base> outcome = execute(context, item, exp, atEntry); 1153 for (Base base : outcome) 1154 if (base != null) 1155 work.add(base); 1156 } 1157 break; 1158 case Function: 1159 List<Base> work2 = evaluateFunction(context, focus, exp); 1160 work.addAll(work2); 1161 break; 1162 case Constant: 1163 Base b = resolveConstant(context, exp.getConstant(), false); 1164 if (b != null) 1165 work.add(b); 1166 break; 1167 case Group: 1168 work2 = execute(context, focus, exp.getGroup(), atEntry); 1169 work.addAll(work2); 1170 } 1171 1172 if (exp.getInner() != null) 1173 work = execute(context, work, exp.getInner(), false); 1174 1175 if (exp.isProximal() && exp.getOperation() != null) { 1176 ExpressionNode next = exp.getOpNext(); 1177 ExpressionNode last = exp; 1178 while (next != null) { 1179 List<Base> work2 = preOperate(work, last.getOperation()); 1180 if (work2 != null) 1181 work = work2; 1182 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 1183 work2 = executeTypeName(context, focus, next, false); 1184 work = operate(work, last.getOperation(), work2); 1185 } else { 1186 work2 = execute(context, focus, next, true); 1187 work = operate(work, last.getOperation(), work2); 1188// System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); 1189 } 1190 last = next; 1191 next = next.getOpNext(); 1192 } 1193 } 1194// System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); 1195 return work; 1196 } 1197 1198 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 1199 List<Base> result = new ArrayList<Base>(); 1200 if (next.getInner() != null) 1201 result.add(new StringType(next.getName()+"."+next.getInner().getName())); 1202 else 1203 result.add(new StringType(next.getName())); 1204 return result; 1205 } 1206 1207 1208 private List<Base> preOperate(List<Base> left, Operation operation) throws PathEngineException { 1209 if (left.size() == 0) 1210 return null; 1211 switch (operation) { 1212 case And: 1213 return isBoolean(left, false) ? makeBoolean(false) : null; 1214 case Or: 1215 return isBoolean(left, true) ? makeBoolean(true) : null; 1216 case Implies: 1217 Equality v = asBool(left); 1218 return v == Equality.False ? makeBoolean(true) : null; 1219 default: 1220 return null; 1221 } 1222 } 1223 1224 private List<Base> makeBoolean(boolean b) { 1225 List<Base> res = new ArrayList<Base>(); 1226 res.add(new BooleanType(b).noExtensions()); 1227 return res; 1228 } 1229 1230 private List<Base> makeNull() { 1231 List<Base> res = new ArrayList<Base>(); 1232 return res; 1233 } 1234 1235 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1236 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 1237 } 1238 1239 private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1240 TypeDetails result = new TypeDetails(null); 1241 switch (exp.getKind()) { 1242 case Name: 1243 if (atEntry && exp.getName().equals("$this")) 1244 result.update(context.getThisItem()); 1245 else if (atEntry && exp.getName().equals("$total")) 1246 result.update(anything(CollectionStatus.UNORDERED)); 1247 else if (atEntry && focus == null) 1248 result.update(executeContextType(context, exp.getName())); 1249 else { 1250 for (String s : focus.getTypes()) { 1251 result.update(executeType(s, exp, atEntry)); 1252 } 1253 if (result.hasNoTypes()) 1254 throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe()); 1255 } 1256 break; 1257 case Function: 1258 result.update(evaluateFunctionType(context, focus, exp)); 1259 break; 1260 case Unary: 1261 result.addType("integer"); 1262 break; 1263 case Constant: 1264 result.update(resolveConstantType(context, exp.getConstant())); 1265 break; 1266 case Group: 1267 result.update(executeType(context, focus, exp.getGroup(), atEntry)); 1268 } 1269 exp.setTypes(result); 1270 1271 if (exp.getInner() != null) { 1272 result = executeType(context, result, exp.getInner(), false); 1273 } 1274 1275 if (exp.isProximal() && exp.getOperation() != null) { 1276 ExpressionNode next = exp.getOpNext(); 1277 ExpressionNode last = exp; 1278 while (next != null) { 1279 TypeDetails work; 1280 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) 1281 work = executeTypeName(context, focus, next, atEntry); 1282 else 1283 work = executeType(context, focus, next, atEntry); 1284 result = operateTypes(result, last.getOperation(), work); 1285 last = next; 1286 next = next.getOpNext(); 1287 } 1288 exp.setOpTypes(result); 1289 } 1290 return result; 1291 } 1292 1293 private Base resolveConstant(ExecutionContext context, Base constant, boolean beforeContext) throws PathEngineException { 1294 if (!(constant instanceof FHIRConstant)) 1295 return constant; 1296 FHIRConstant c = (FHIRConstant) constant; 1297 if (c.getValue().startsWith("%")) { 1298 return resolveConstant(context, c.getValue(), beforeContext); 1299 } else if (c.getValue().startsWith("@")) { 1300 return processDateConstant(context.appInfo, c.getValue().substring(1)); 1301 } else 1302 throw new PathEngineException("Invaild FHIR Constant "+c.getValue()); 1303 } 1304 1305 private Base processDateConstant(Object appInfo, String value) throws PathEngineException { 1306 if (value.startsWith("T")) 1307 return new TimeType(value.substring(1)).noExtensions(); 1308 String v = value; 1309 if (v.length() > 10) { 1310 int i = v.substring(10).indexOf("-"); 1311 if (i == -1) 1312 i = v.substring(10).indexOf("+"); 1313 if (i == -1) 1314 i = v.substring(10).indexOf("Z"); 1315 v = i == -1 ? value : v.substring(0, 10+i); 1316 } 1317 if (v.length() > 10) 1318 return new DateTimeType(value).noExtensions(); 1319 else 1320 return new DateType(value).noExtensions(); 1321 } 1322 1323 1324 private Base resolveConstant(ExecutionContext context, String s, boolean beforeContext) throws PathEngineException { 1325 if (s.equals("%sct")) 1326 return new StringType("http://snomed.info/sct").noExtensions(); 1327 else if (s.equals("%loinc")) 1328 return new StringType("http://loinc.org").noExtensions(); 1329 else if (s.equals("%ucum")) 1330 return new StringType("http://unitsofmeasure.org").noExtensions(); 1331 else if (s.equals("%resource")) { 1332 if (context.resource == null) 1333 throw new PathEngineException("Cannot use %resource in this context"); 1334 return context.resource; 1335 } else if (s.equals("%context")) { 1336 return context.context; 1337 } else if (s.equals("%us-zip")) 1338 return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions(); 1339 else if (s.startsWith("%`vs-")) 1340 return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions(); 1341 else if (s.startsWith("%`cs-")) 1342 return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions(); 1343 else if (s.startsWith("%`ext-")) 1344 return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions(); 1345 else if (hostServices == null) 1346 throw new PathEngineException("Unknown fixed constant '"+s+"'"); 1347 else 1348 return hostServices.resolveConstant(context.appInfo, s.substring(1), beforeContext); 1349 } 1350 1351 1352 private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException { 1353 StringBuilder b = new StringBuilder(); 1354 int i = 1; 1355 while (i < s.length()-1) { 1356 char ch = s.charAt(i); 1357 if (ch == '\\') { 1358 i++; 1359 switch (s.charAt(i)) { 1360 case 't': 1361 b.append('\t'); 1362 break; 1363 case 'r': 1364 b.append('\r'); 1365 break; 1366 case 'n': 1367 b.append('\n'); 1368 break; 1369 case 'f': 1370 b.append('\f'); 1371 break; 1372 case '\'': 1373 b.append('\''); 1374 break; 1375 case '"': 1376 b.append('"'); 1377 break; 1378 case '`': 1379 b.append('`'); 1380 break; 1381 case '\\': 1382 b.append('\\'); 1383 break; 1384 case '/': 1385 b.append('/'); 1386 break; 1387 case 'u': 1388 i++; 1389 int uc = Integer.parseInt(s.substring(i, i+4), 16); 1390 b.append((char) uc); 1391 i = i + 3; 1392 break; 1393 default: 1394 throw lexer.error("Unknown character escape \\"+s.charAt(i)); 1395 } 1396 i++; 1397 } else { 1398 b.append(ch); 1399 i++; 1400 } 1401 } 1402 return b.toString(); 1403 } 1404 1405 1406 private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException { 1407 switch (operation) { 1408 case Equals: return opEquals(left, right); 1409 case Equivalent: return opEquivalent(left, right); 1410 case NotEquals: return opNotEquals(left, right); 1411 case NotEquivalent: return opNotEquivalent(left, right); 1412 case LessThan: return opLessThan(left, right); 1413 case Greater: return opGreater(left, right); 1414 case LessOrEqual: return opLessOrEqual(left, right); 1415 case GreaterOrEqual: return opGreaterOrEqual(left, right); 1416 case Union: return opUnion(left, right); 1417 case In: return opIn(left, right); 1418 case MemberOf: return opMemberOf(left, right); 1419 case Contains: return opContains(left, right); 1420 case Or: return opOr(left, right); 1421 case And: return opAnd(left, right); 1422 case Xor: return opXor(left, right); 1423 case Implies: return opImplies(left, right); 1424 case Plus: return opPlus(left, right); 1425 case Times: return opTimes(left, right); 1426 case Minus: return opMinus(left, right); 1427 case Concatenate: return opConcatenate(left, right); 1428 case DivideBy: return opDivideBy(left, right); 1429 case Div: return opDiv(left, right); 1430 case Mod: return opMod(left, right); 1431 case Is: return opIs(left, right); 1432 case As: return opAs(left, right); 1433 default: 1434 throw new Error("Not Done Yet: "+operation.toCode()); 1435 } 1436 } 1437 1438 private List<Base> opAs(List<Base> left, List<Base> right) { 1439 List<Base> result = new ArrayList<>(); 1440 if (right.size() != 1) 1441 return result; 1442 else { 1443 String tn = convertToString(right); 1444 for (Base nextLeft : left) { 1445 if (tn.equals(nextLeft.fhirType())) 1446 result.add(nextLeft); 1447 } 1448 } 1449 return result; 1450 } 1451 1452 1453 private List<Base> opIs(List<Base> left, List<Base> right) { 1454 List<Base> result = new ArrayList<Base>(); 1455 if (left.size() != 1 || right.size() != 1) 1456 result.add(new BooleanType(false).noExtensions()); 1457 else { 1458 String tn = convertToString(right); 1459 if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element) 1460 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 1461 else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions()) 1462 result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions()); 1463 else 1464 result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions()); 1465 } 1466 return result; 1467 } 1468 1469 1470 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) { 1471 switch (operation) { 1472 case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1473 case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1474 case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1475 case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1476 case LessThan: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1477 case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1478 case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1479 case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1480 case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1481 case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 1482 case Union: return left.union(right); 1483 case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1484 case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1485 case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1486 case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1487 case Times: 1488 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 1489 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1490 result.addType(TypeDetails.FP_Integer); 1491 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1492 result.addType(TypeDetails.FP_Decimal); 1493 return result; 1494 case DivideBy: 1495 result = new TypeDetails(CollectionStatus.SINGLETON); 1496 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1497 result.addType(TypeDetails.FP_Decimal); 1498 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1499 result.addType(TypeDetails.FP_Decimal); 1500 return result; 1501 case Concatenate: 1502 result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 1503 return result; 1504 case Plus: 1505 result = new TypeDetails(CollectionStatus.SINGLETON); 1506 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1507 result.addType(TypeDetails.FP_Integer); 1508 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1509 result.addType(TypeDetails.FP_Decimal); 1510 else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) 1511 result.addType(TypeDetails.FP_String); 1512 return result; 1513 case Minus: 1514 result = new TypeDetails(CollectionStatus.SINGLETON); 1515 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1516 result.addType(TypeDetails.FP_Integer); 1517 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1518 result.addType(TypeDetails.FP_Decimal); 1519 return result; 1520 case Div: 1521 case Mod: 1522 result = new TypeDetails(CollectionStatus.SINGLETON); 1523 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1524 result.addType(TypeDetails.FP_Integer); 1525 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1526 result.addType(TypeDetails.FP_Decimal); 1527 return result; 1528 case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1529 case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1530 case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 1531 default: 1532 return null; 1533 } 1534 } 1535 1536 1537 private List<Base> opEquals(List<Base> left, List<Base> right) { 1538 if (left.size() == 0 || right.size() == 0) 1539 return new ArrayList<Base>(); 1540 1541 if (left.size() != right.size()) 1542 return makeBoolean(false); 1543 1544 boolean res = true; 1545 boolean nil = false; 1546 for (int i = 0; i < left.size(); i++) { 1547 Boolean eq = doEquals(left.get(i), right.get(i)); 1548 if (eq == null) 1549 nil = true; 1550 else if (eq == false) { 1551 res = false; 1552 break; 1553 } 1554 } 1555 if (!res) 1556 return makeBoolean(res); 1557 else if (nil) 1558 return new ArrayList<Base>(); 1559 else 1560 return makeBoolean(res); 1561 } 1562 1563 private List<Base> opNotEquals(List<Base> left, List<Base> right) { 1564 if (!legacyMode && (left.size() == 0 || right.size() == 0)) 1565 return new ArrayList<Base>(); 1566 1567 if (left.size() != right.size()) 1568 return makeBoolean(true); 1569 1570 boolean res = true; 1571 boolean nil = false; 1572 for (int i = 0; i < left.size(); i++) { 1573 Boolean eq = doEquals(left.get(i), right.get(i)); 1574 if (eq == null) 1575 nil = true; 1576 else if (eq == true) { 1577 res = false; 1578 break; 1579 } 1580 } 1581 if (!res) 1582 return makeBoolean(res); 1583 else if (nil) 1584 return new ArrayList<Base>(); 1585 else 1586 return makeBoolean(res); 1587 } 1588 1589 private String removeTrailingZeros(String s) { 1590 if (Utilities.noString(s)) 1591 return ""; 1592 int i = s.length()-1; 1593 boolean done = false; 1594 boolean dot = false; 1595 while (i > 0 && !done) { 1596 if (s.charAt(i) == '.') { 1597 i--; 1598 dot = true; 1599 } 1600 else if (!dot && s.charAt(i) == '0') 1601 i--; 1602 else 1603 done = true; 1604 } 1605 return s.substring(0, i+1); 1606 } 1607 1608 private boolean decEqual(String left, String right) { 1609 left = removeTrailingZeros(left); 1610 right = removeTrailingZeros(right); 1611 return left.equals(right); 1612 } 1613 1614 private Boolean compareDates(BaseDateTimeType left, BaseDateTimeType right) { 1615 return left.equalsUsingFhirPathRules(right); 1616 } 1617 1618 private Boolean doEquals(Base left, Base right) { 1619 if (left instanceof Quantity && right instanceof Quantity) 1620 return qtyEqual((Quantity) left, (Quantity) right); 1621 else if (left.isDateTime() && right.isDateTime()) { 1622 return compareDates(left.dateTimeValue(), right.dateTimeValue()); 1623 } else if (left instanceof DecimalType || right instanceof DecimalType) 1624 return decEqual(left.primitiveValue(), right.primitiveValue()); 1625 else if (left.isPrimitive() && right.isPrimitive()) 1626 return Base.equals(left.primitiveValue(), right.primitiveValue()); 1627 else 1628 return Base.compareDeep(left, right, false); 1629 } 1630 1631 1632 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 1633 if (left instanceof Quantity && right instanceof Quantity) 1634 return qtyEquivalent((Quantity) left, (Quantity) right); 1635 if (left.hasType("integer") && right.hasType("integer")) 1636 return doEquals(left, right); 1637 if (left.hasType("boolean") && right.hasType("boolean")) 1638 return doEquals(left, right); 1639 if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) 1640 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1641 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) 1642 return compareDateTimeElements(left, right, true) == 0; 1643 if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) 1644 return Utilities.equivalent(convertToString(left), convertToString(right)); 1645 1646 throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType())); 1647 } 1648 1649 private boolean qtyEqual(Quantity left, Quantity right) { 1650 if (worker.getUcumService() != null) { 1651 DecimalType dl = qtyToCanonical(left); 1652 DecimalType dr = qtyToCanonical(right); 1653 if (dl != null && dr != null) 1654 return doEquals(dl, dr); 1655 } 1656 return left.equals(right); 1657 } 1658 1659 private DecimalType qtyToCanonical(Quantity q) { 1660 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 1661 return null; 1662 try { 1663 Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 1664 Pair c = worker.getUcumService().getCanonicalForm(p); 1665 return new DecimalType(c.getValue().asDecimal()); 1666 } catch (UcumException e) { 1667 return null; 1668 } 1669 } 1670 1671 private Base pairToQty(Pair p) { 1672 return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions(); 1673 } 1674 1675 1676 private Pair qtyToPair(Quantity q) { 1677 if (!"http://unitsofmeasure.org".equals(q.getSystem())) 1678 return null; 1679 try { 1680 return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode()); 1681 } catch (UcumException e) { 1682 return null; 1683 } 1684 } 1685 1686 1687 private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException { 1688 if (worker.getUcumService() != null) { 1689 DecimalType dl = qtyToCanonical(left); 1690 DecimalType dr = qtyToCanonical(right); 1691 if (dl != null && dr != null) 1692 return doEquivalent(dl, dr); 1693 } 1694 return left.equals(right); 1695 } 1696 1697 1698 1699 private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1700 if (left.size() != right.size()) 1701 return makeBoolean(false); 1702 1703 boolean res = true; 1704 for (int i = 0; i < left.size(); i++) { 1705 boolean found = false; 1706 for (int j = 0; j < right.size(); j++) { 1707 if (doEquivalent(left.get(i), right.get(j))) { 1708 found = true; 1709 break; 1710 } 1711 } 1712 if (!found) { 1713 res = false; 1714 break; 1715 } 1716 } 1717 return makeBoolean(res); 1718 } 1719 1720 private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1721 if (left.size() != right.size()) 1722 return makeBoolean(true); 1723 1724 boolean res = true; 1725 for (int i = 0; i < left.size(); i++) { 1726 boolean found = false; 1727 for (int j = 0; j < right.size(); j++) { 1728 if (doEquivalent(left.get(i), right.get(j))) { 1729 found = true; 1730 break; 1731 } 1732 } 1733 if (!found) { 1734 res = false; 1735 break; 1736 } 1737 } 1738 return makeBoolean(!res); 1739 } 1740 1741 private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url"}; 1742 1743 private List<Base> opLessThan(List<Base> left, List<Base> right) throws FHIRException { 1744 if (left.size() == 0 || right.size() == 0) 1745 return new ArrayList<Base>(); 1746 1747 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1748 Base l = left.get(0); 1749 Base r = right.get(0); 1750 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1751 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1752 else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 1753 return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); 1754 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1755 return makeBoolean(compareDateTimeElements(l, r, false) < 0); 1756 else if ((l.hasType("time")) && (r.hasType("time"))) 1757 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1758 else 1759 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1760 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1761 List<Base> lUnit = left.get(0).listChildrenByName("code"); 1762 List<Base> rUnit = right.get(0).listChildrenByName("code"); 1763 if (Base.compareDeep(lUnit, rUnit, true)) { 1764 return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1765 } else { 1766 if (worker.getUcumService() == null) 1767 return makeBoolean(false); 1768 else { 1769 List<Base> dl = new ArrayList<Base>(); 1770 dl.add(qtyToCanonical((Quantity) left.get(0))); 1771 List<Base> dr = new ArrayList<Base>(); 1772 dr.add(qtyToCanonical((Quantity) right.get(0))); 1773 return opLessThan(dl, dr); 1774 } 1775 } 1776 } 1777 return new ArrayList<Base>(); 1778 } 1779 1780 private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException { 1781 if (left.size() == 0 || right.size() == 0) 1782 return new ArrayList<Base>(); 1783 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1784 Base l = left.get(0); 1785 Base r = right.get(0); 1786 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1787 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1788 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1789 return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); 1790 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1791 return makeBoolean(compareDateTimeElements(l, r, false) > 0); 1792 else if ((l.hasType("time")) && (r.hasType("time"))) 1793 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1794 else 1795 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1796 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1797 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1798 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1799 if (Base.compareDeep(lUnit, rUnit, true)) { 1800 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1801 } else { 1802 if (worker.getUcumService() == null) 1803 return makeBoolean(false); 1804 else { 1805 List<Base> dl = new ArrayList<Base>(); 1806 dl.add(qtyToCanonical((Quantity) left.get(0))); 1807 List<Base> dr = new ArrayList<Base>(); 1808 dr.add(qtyToCanonical((Quantity) right.get(0))); 1809 return opGreater(dl, dr); 1810 } 1811 } 1812 } 1813 return new ArrayList<Base>(); 1814 } 1815 1816 private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1817 if (left.size() == 0 || right.size() == 0) 1818 return new ArrayList<Base>(); 1819 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1820 Base l = left.get(0); 1821 Base r = right.get(0); 1822 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1823 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1824 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1825 return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); 1826 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1827 return makeBoolean(compareDateTimeElements(l, r, false) <= 0); 1828 else if ((l.hasType("time")) && (r.hasType("time"))) 1829 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1830 else 1831 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1832 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1833 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 1834 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 1835 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 1836 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 1837 if ((lunit == null && runit == null) || lunit.equals(runit)) { 1838 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1839 } else { 1840 if (worker.getUcumService() == null) 1841 return makeBoolean(false); 1842 else { 1843 List<Base> dl = new ArrayList<Base>(); 1844 dl.add(qtyToCanonical((Quantity) left.get(0))); 1845 List<Base> dr = new ArrayList<Base>(); 1846 dr.add(qtyToCanonical((Quantity) right.get(0))); 1847 return opLessOrEqual(dl, dr); 1848 } 1849 } 1850 } 1851 return new ArrayList<Base>(); 1852 } 1853 1854 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1855 if (left.size() == 0 || right.size() == 0) 1856 return new ArrayList<Base>(); 1857 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1858 Base l = left.get(0); 1859 Base r = right.get(0); 1860 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1861 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1862 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1863 return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); 1864 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1865 return makeBoolean(compareDateTimeElements(l, r, false) >= 0); 1866 else if ((l.hasType("time")) && (r.hasType("time"))) 1867 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1868 else 1869 throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType()); 1870 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1871 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1872 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1873 if (Base.compareDeep(lUnit, rUnit, true)) { 1874 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1875 } else { 1876 if (worker.getUcumService() == null) 1877 return makeBoolean(false); 1878 else { 1879 List<Base> dl = new ArrayList<Base>(); 1880 dl.add(qtyToCanonical((Quantity) left.get(0))); 1881 List<Base> dr = new ArrayList<Base>(); 1882 dr.add(qtyToCanonical((Quantity) right.get(0))); 1883 return opGreaterOrEqual(dl, dr); 1884 } 1885 } 1886 } 1887 return new ArrayList<Base>(); 1888 } 1889 1890 private List<Base> opMemberOf(List<Base> left, List<Base> right) throws FHIRException { 1891 boolean ans = false; 1892 ValueSet vs = worker.fetchResource(ValueSet.class, right.get(0).primitiveValue()); 1893 if (vs != null) { 1894 for (Base l : left) { 1895 if (l.fhirType().equals("code")) { 1896 if (worker.validateCode(l.castToCoding(l), vs).isOk()) 1897 ans = true; 1898 } else if (l.fhirType().equals("Coding")) { 1899 if (worker.validateCode(l.castToCoding(l), vs).isOk()) 1900 ans = true; 1901 } else if (l.fhirType().equals("CodeableConcept")) { 1902 if (worker.validateCode(l.castToCodeableConcept(l), vs).isOk()) 1903 ans = true; 1904 } 1905 } 1906 } 1907 return makeBoolean(ans); 1908 } 1909 1910 private List<Base> opIn(List<Base> left, List<Base> right) throws FHIRException { 1911 if (left.size() == 0) 1912 return new ArrayList<Base>(); 1913 if (right.size() == 0) 1914 return makeBoolean(false); 1915 boolean ans = true; 1916 for (Base l : left) { 1917 boolean f = false; 1918 for (Base r : right) { 1919 Boolean eq = doEquals(l, r); 1920 if (eq != null && eq == true) { 1921 f = true; 1922 break; 1923 } 1924 } 1925 if (!f) { 1926 ans = false; 1927 break; 1928 } 1929 } 1930 return makeBoolean(ans); 1931 } 1932 1933 private List<Base> opContains(List<Base> left, List<Base> right) { 1934 if (left.size() == 0 || right.size() == 0) 1935 return new ArrayList<Base>(); 1936 boolean ans = true; 1937 for (Base r : right) { 1938 boolean f = false; 1939 for (Base l : left) { 1940 Boolean eq = doEquals(l, r); 1941 if (eq != null && eq == true) { 1942 f = true; 1943 break; 1944 } 1945 } 1946 if (!f) { 1947 ans = false; 1948 break; 1949 } 1950 } 1951 return makeBoolean(ans); 1952 } 1953 1954 private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException { 1955 if (left.size() == 0 || right.size() == 0) 1956 return new ArrayList<Base>(); 1957 if (left.size() > 1) 1958 throw new PathEngineException("Error performing +: left operand has more than one value"); 1959 if (!left.get(0).isPrimitive()) 1960 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1961 if (right.size() > 1) 1962 throw new PathEngineException("Error performing +: right operand has more than one value"); 1963 if (!right.get(0).isPrimitive()) 1964 throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType())); 1965 1966 List<Base> result = new ArrayList<Base>(); 1967 Base l = left.get(0); 1968 Base r = right.get(0); 1969 if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 1970 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 1971 else if (l.hasType("integer") && r.hasType("integer")) 1972 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 1973 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1974 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 1975 else 1976 throw new PathEngineException(String.format("Error performing +: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 1977 return result; 1978 } 1979 1980 private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException { 1981 if (left.size() == 0 || right.size() == 0) 1982 return new ArrayList<Base>(); 1983 if (left.size() > 1) 1984 throw new PathEngineException("Error performing *: left operand has more than one value"); 1985 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 1986 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1987 if (right.size() > 1) 1988 throw new PathEngineException("Error performing *: right operand has more than one value"); 1989 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 1990 throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType())); 1991 1992 List<Base> result = new ArrayList<Base>(); 1993 Base l = left.get(0); 1994 Base r = right.get(0); 1995 1996 if (l.hasType("integer") && r.hasType("integer")) 1997 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 1998 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1999 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 2000 else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2001 Pair pl = qtyToPair((Quantity) l); 2002 Pair pr = qtyToPair((Quantity) r); 2003 Pair p; 2004 try { 2005 p = worker.getUcumService().multiply(pl, pr); 2006 result.add(pairToQty(p)); 2007 } catch (UcumException e) { 2008 throw new PathEngineException(e.getMessage(), e); 2009 } 2010 } else 2011 throw new PathEngineException(String.format("Error performing *: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 2012 return result; 2013 } 2014 2015 2016 private List<Base> opConcatenate(List<Base> left, List<Base> right) throws PathEngineException { 2017 if (left.size() > 1) 2018 throw new PathEngineException("Error performing &: left operand has more than one value"); 2019 if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) 2020 throw new PathEngineException(String.format("Error performing &: left operand has the wrong type (%s)", left.get(0).fhirType())); 2021 if (right.size() > 1) 2022 throw new PathEngineException("Error performing &: right operand has more than one value"); 2023 if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) 2024 throw new PathEngineException(String.format("Error performing &: right operand has the wrong type (%s)", right.get(0).fhirType())); 2025 2026 List<Base> result = new ArrayList<Base>(); 2027 String l = left.size() == 0 ? "" : left.get(0).primitiveValue(); 2028 String r = right.size() == 0 ? "" : right.get(0).primitiveValue(); 2029 result.add(new StringType(l + r)); 2030 return result; 2031 } 2032 2033 private List<Base> opUnion(List<Base> left, List<Base> right) { 2034 List<Base> result = new ArrayList<Base>(); 2035 for (Base item : left) { 2036 if (!doContains(result, item)) 2037 result.add(item); 2038 } 2039 for (Base item : right) { 2040 if (!doContains(result, item)) 2041 result.add(item); 2042 } 2043 return result; 2044 } 2045 2046 private boolean doContains(List<Base> list, Base item) { 2047 for (Base test : list) { 2048 Boolean eq = doEquals(test, item); 2049 if (eq != null && eq == true) 2050 return true; 2051 } 2052 return false; 2053 } 2054 2055 2056 private List<Base> opAnd(List<Base> left, List<Base> right) throws PathEngineException { 2057 Equality l = asBool(left); 2058 Equality r = asBool(right); 2059 switch (l) { 2060 case False: return makeBoolean(false); 2061 case Null: 2062 if (r == Equality.False) 2063 return makeBoolean(false); 2064 else 2065 return makeNull(); 2066 case True: 2067 switch (r) { 2068 case False: return makeBoolean(false); 2069 case Null: return makeNull(); 2070 case True: return makeBoolean(true); 2071 } 2072 } 2073 return makeNull(); 2074 } 2075 2076 private boolean isBoolean(List<Base> list, boolean b) { 2077 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 2078 } 2079 2080 private List<Base> opOr(List<Base> left, List<Base> right) throws PathEngineException { 2081 Equality l = asBool(left); 2082 Equality r = asBool(right); 2083 switch (l) { 2084 case True: return makeBoolean(true); 2085 case Null: 2086 if (r == Equality.True) 2087 return makeBoolean(true); 2088 else 2089 return makeNull(); 2090 case False: 2091 switch (r) { 2092 case False: return makeBoolean(false); 2093 case Null: return makeNull(); 2094 case True: return makeBoolean(true); 2095 } 2096 } 2097 return makeNull(); 2098 } 2099 2100 private List<Base> opXor(List<Base> left, List<Base> right) throws PathEngineException { 2101 Equality l = asBool(left); 2102 Equality r = asBool(right); 2103 switch (l) { 2104 case True: 2105 switch (r) { 2106 case False: return makeBoolean(true); 2107 case True: return makeBoolean(false); 2108 case Null: return makeNull(); 2109 } 2110 case Null: 2111 return makeNull(); 2112 case False: 2113 switch (r) { 2114 case False: return makeBoolean(false); 2115 case True: return makeBoolean(true); 2116 case Null: return makeNull(); 2117 } 2118 } 2119 return makeNull(); 2120 } 2121 2122 private List<Base> opImplies(List<Base> left, List<Base> right) throws PathEngineException { 2123 Equality eq = asBool(left); 2124 if (eq == Equality.False) 2125 return makeBoolean(true); 2126 else if (right.size() == 0) 2127 return makeNull(); 2128 else switch (asBool(right)) { 2129 case False: return eq == Equality.Null ? makeNull() : makeBoolean(false); 2130 case Null: return makeNull(); 2131 case True: return makeBoolean(true); 2132 } 2133 return makeNull(); 2134 } 2135 2136 2137 private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException { 2138 if (left.size() == 0 || right.size() == 0) 2139 return new ArrayList<Base>(); 2140 if (left.size() > 1) 2141 throw new PathEngineException("Error performing -: left operand has more than one value"); 2142 if (!left.get(0).isPrimitive()) 2143 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2144 if (right.size() > 1) 2145 throw new PathEngineException("Error performing -: right operand has more than one value"); 2146 if (!right.get(0).isPrimitive()) 2147 throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType())); 2148 2149 List<Base> result = new ArrayList<Base>(); 2150 Base l = left.get(0); 2151 Base r = right.get(0); 2152 2153 if (l.hasType("integer") && r.hasType("integer")) 2154 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 2155 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 2156 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 2157 else 2158 throw new PathEngineException(String.format("Error performing -: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 2159 return result; 2160 } 2161 2162 private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException { 2163 if (left.size() == 0 || right.size() == 0) 2164 return new ArrayList<Base>(); 2165 if (left.size() > 1) 2166 throw new PathEngineException("Error performing /: left operand has more than one value"); 2167 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2168 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 2169 if (right.size() > 1) 2170 throw new PathEngineException("Error performing /: right operand has more than one value"); 2171 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2172 throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType())); 2173 2174 List<Base> result = new ArrayList<Base>(); 2175 Base l = left.get(0); 2176 Base r = right.get(0); 2177 2178 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 2179 Decimal d1; 2180 try { 2181 d1 = new Decimal(l.primitiveValue()); 2182 Decimal d2 = new Decimal(r.primitiveValue()); 2183 result.add(new DecimalType(d1.divide(d2).asDecimal())); 2184 } catch (UcumException e) { 2185 throw new PathEngineException(e); 2186 } 2187 } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) { 2188 Pair pl = qtyToPair((Quantity) l); 2189 Pair pr = qtyToPair((Quantity) r); 2190 Pair p; 2191 try { 2192 p = worker.getUcumService().multiply(pl, pr); 2193 result.add(pairToQty(p)); 2194 } catch (UcumException e) { 2195 throw new PathEngineException(e.getMessage(), e); 2196 } 2197 } else 2198 throw new PathEngineException(String.format("Error performing /: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 2199 return result; 2200 } 2201 2202 private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException { 2203 if (left.size() == 0 || right.size() == 0) 2204 return new ArrayList<Base>(); 2205 if (left.size() > 1) 2206 throw new PathEngineException("Error performing div: left operand has more than one value"); 2207 if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) 2208 throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType())); 2209 if (right.size() > 1) 2210 throw new PathEngineException("Error performing div: right operand has more than one value"); 2211 if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) 2212 throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType())); 2213 2214 List<Base> result = new ArrayList<Base>(); 2215 Base l = left.get(0); 2216 Base r = right.get(0); 2217 2218 if (l.hasType("integer") && r.hasType("integer")) 2219 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue()))); 2220 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2221 Decimal d1; 2222 try { 2223 d1 = new Decimal(l.primitiveValue()); 2224 Decimal d2 = new Decimal(r.primitiveValue()); 2225 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 2226 } catch (UcumException e) { 2227 throw new PathEngineException(e); 2228 } 2229 } 2230 else 2231 throw new PathEngineException(String.format("Error performing div: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 2232 return result; 2233 } 2234 2235 private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException { 2236 if (left.size() == 0 || right.size() == 0) 2237 return new ArrayList<Base>(); 2238 if (left.size() > 1) 2239 throw new PathEngineException("Error performing mod: left operand has more than one value"); 2240 if (!left.get(0).isPrimitive()) 2241 throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType())); 2242 if (right.size() > 1) 2243 throw new PathEngineException("Error performing mod: right operand has more than one value"); 2244 if (!right.get(0).isPrimitive()) 2245 throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType())); 2246 2247 List<Base> result = new ArrayList<Base>(); 2248 Base l = left.get(0); 2249 Base r = right.get(0); 2250 2251 if (l.hasType("integer") && r.hasType("integer")) 2252 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue()))); 2253 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 2254 Decimal d1; 2255 try { 2256 d1 = new Decimal(l.primitiveValue()); 2257 Decimal d2 = new Decimal(r.primitiveValue()); 2258 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 2259 } catch (UcumException e) { 2260 throw new PathEngineException(e); 2261 } 2262 } 2263 else 2264 throw new PathEngineException(String.format("Error performing mod: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType())); 2265 return result; 2266 } 2267 2268 2269 private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant) throws PathEngineException { 2270 if (constant instanceof BooleanType) 2271 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2272 else if (constant instanceof IntegerType) 2273 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2274 else if (constant instanceof DecimalType) 2275 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 2276 else if (constant instanceof Quantity) 2277 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 2278 else if (constant instanceof FHIRConstant) 2279 return resolveConstantType(context, ((FHIRConstant) constant).getValue()); 2280 else 2281 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2282 } 2283 2284 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException { 2285 if (s.startsWith("@")) { 2286 if (s.startsWith("@T")) 2287 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 2288 else 2289 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2290 } else if (s.equals("%sct")) 2291 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2292 else if (s.equals("%loinc")) 2293 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2294 else if (s.equals("%ucum")) 2295 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2296 else if (s.equals("%resource")) { 2297 if (context.resource == null) 2298 throw new PathEngineException("%resource cannot be used in this context"); 2299 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 2300 } else if (s.equals("%context")) { 2301 return context.context; 2302 } else if (s.equals("%map-codes")) 2303 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2304 else if (s.equals("%us-zip")) 2305 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2306 else if (s.startsWith("%`vs-")) 2307 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2308 else if (s.startsWith("%`cs-")) 2309 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2310 else if (s.startsWith("%`ext-")) 2311 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2312 else if (hostServices == null) 2313 throw new PathEngineException("Unknown fixed constant type for '"+s+"'"); 2314 else 2315 return hostServices.resolveConstantType(context.appInfo, s); 2316 } 2317 2318 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 2319 List<Base> result = new ArrayList<Base>(); 2320 if (atEntry && context.appInfo != null && hostServices != null) { 2321 // we'll see if the name matches a constant known by the context. 2322 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true); 2323 if (temp != null) { 2324 result.add(temp); 2325 return result; 2326 } 2327 } 2328 if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 2329 if (item.isResource() && item.fhirType().equals(exp.getName())) 2330 result.add(item); 2331 } else 2332 getChildrenByName(item, exp.getName(), result); 2333 if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) { 2334 // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. 2335 // (if the name does match, and the user wants to get the constant value, they'll have to try harder... 2336 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false); 2337 if (temp != null) { 2338 result.add(temp); 2339 } 2340 } 2341 return result; 2342 } 2343 2344 private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException { 2345 if (hostServices == null) 2346 throw new PathEngineException("Unable to resolve context reference since no host services are provided"); 2347 return hostServices.resolveConstantType(context.appInfo, name); 2348 } 2349 2350 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 2351 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up 2352 return new TypeDetails(CollectionStatus.SINGLETON, type); 2353 TypeDetails result = new TypeDetails(null); 2354 getChildTypesByName(type, exp.getName(), result); 2355 return result; 2356 } 2357 2358 2359 private String hashTail(String type) { 2360 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); 2361 } 2362 2363 2364 @SuppressWarnings("unchecked") 2365 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { 2366 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 2367 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) 2368 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2369 else 2370 for (ExpressionNode expr : exp.getParameters()) { 2371 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) 2372 paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); 2373 else 2374 paramTypes.add(executeType(context, focus, expr, true)); 2375 } 2376 switch (exp.getFunction()) { 2377 case Empty : 2378 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2379 case Not : 2380 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2381 case Exists : 2382 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2383 case SubsetOf : { 2384 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 2385 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2386 } 2387 case SupersetOf : { 2388 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 2389 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2390 } 2391 case IsDistinct : 2392 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2393 case Distinct : 2394 return focus; 2395 case Count : 2396 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2397 case Where : 2398 return focus; 2399 case Select : 2400 return anything(focus.getCollectionStatus()); 2401 case All : 2402 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2403 case Repeat : 2404 return anything(focus.getCollectionStatus()); 2405 case Aggregate : 2406 return anything(focus.getCollectionStatus()); 2407 case Item : { 2408 checkOrdered(focus, "item"); 2409 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2410 return focus; 2411 } 2412 case As : { 2413 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2414 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 2415 } 2416 case OfType : { 2417 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2418 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 2419 } 2420 case Type : { 2421 boolean s = false; 2422 boolean c = false; 2423 for (ProfiledType pt : focus.getProfiledTypes()) { 2424 s = s || pt.isSystemType(); 2425 c = c || !pt.isSystemType(); 2426 } 2427 if (s && c) 2428 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo); 2429 else if (s) 2430 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo); 2431 else 2432 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo); 2433 } 2434 case Is : { 2435 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2436 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2437 } 2438 case Single : 2439 return focus.toSingleton(); 2440 case First : { 2441 checkOrdered(focus, "first"); 2442 return focus.toSingleton(); 2443 } 2444 case Last : { 2445 checkOrdered(focus, "last"); 2446 return focus.toSingleton(); 2447 } 2448 case Tail : { 2449 checkOrdered(focus, "tail"); 2450 return focus; 2451 } 2452 case Skip : { 2453 checkOrdered(focus, "skip"); 2454 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2455 return focus; 2456 } 2457 case Take : { 2458 checkOrdered(focus, "take"); 2459 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2460 return focus; 2461 } 2462 case Union : { 2463 return focus.union(paramTypes.get(0)); 2464 } 2465 case Combine : { 2466 return focus.union(paramTypes.get(0)); 2467 } 2468 case Intersect : { 2469 return focus.intersect(paramTypes.get(0)); 2470 } 2471 case Exclude : { 2472 return focus; 2473 } 2474 case Iif : { 2475 TypeDetails types = new TypeDetails(null); 2476 types.update(paramTypes.get(0)); 2477 if (paramTypes.size() > 1) 2478 types.update(paramTypes.get(1)); 2479 return types; 2480 } 2481 case Lower : { 2482 checkContextString(focus, "lower"); 2483 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2484 } 2485 case Upper : { 2486 checkContextString(focus, "upper"); 2487 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2488 } 2489 case ToChars : { 2490 checkContextString(focus, "toChars"); 2491 return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 2492 } 2493 case Substring : { 2494 checkContextString(focus, "subString"); 2495 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 2496 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2497 } 2498 case StartsWith : { 2499 checkContextString(focus, "startsWith"); 2500 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2501 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2502 } 2503 case EndsWith : { 2504 checkContextString(focus, "endsWith"); 2505 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2506 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2507 } 2508 case Matches : { 2509 checkContextString(focus, "matches"); 2510 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2511 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2512 } 2513 case ReplaceMatches : { 2514 checkContextString(focus, "replaceMatches"); 2515 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2516 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2517 } 2518 case Contains : { 2519 checkContextString(focus, "contains"); 2520 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2521 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2522 } 2523 case Replace : { 2524 checkContextString(focus, "replace"); 2525 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2526 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2527 } 2528 case Length : { 2529 checkContextPrimitive(focus, "length", false); 2530 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2531 } 2532 case Children : 2533 return childTypes(focus, "*"); 2534 case Descendants : 2535 return childTypes(focus, "**"); 2536 case MemberOf : { 2537 checkContextCoded(focus, "memberOf"); 2538 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2539 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2540 } 2541 case Trace : { 2542 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2543 return focus; 2544 } 2545 case Today : 2546 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2547 case Now : 2548 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2549 case Resolve : { 2550 checkContextReference(focus, "resolve"); 2551 return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 2552 } 2553 case Extension : { 2554 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2555 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 2556 } 2557 case AnyTrue: 2558 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2559 case AllTrue: 2560 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2561 case AnyFalse: 2562 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2563 case AllFalse: 2564 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2565 case HasValue : 2566 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2567 case HtmlChecks : 2568 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2569 case Alias : 2570 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2571 return anything(CollectionStatus.SINGLETON); 2572 case AliasAs : 2573 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2574 return focus; 2575 case ToInteger : { 2576 checkContextPrimitive(focus, "toInteger", true); 2577 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 2578 } 2579 case ToDecimal : { 2580 checkContextPrimitive(focus, "toDecimal", true); 2581 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal); 2582 } 2583 case ToString : { 2584 checkContextPrimitive(focus, "toString", true); 2585 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 2586 } 2587 case ToQuantity : { 2588 checkContextPrimitive(focus, "toQuantity", true); 2589 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity); 2590 } 2591 case ToBoolean : { 2592 checkContextPrimitive(focus, "toBoolean", false); 2593 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2594 } 2595 case ToDateTime : { 2596 checkContextPrimitive(focus, "toBoolean", false); 2597 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); 2598 } 2599 case ToTime : { 2600 checkContextPrimitive(focus, "toBoolean", false); 2601 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time); 2602 } 2603 case ConvertsToString : 2604 case ConvertsToQuantity :{ 2605 checkContextPrimitive(focus, exp.getFunction().toCode(), true); 2606 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2607 } 2608 case ConvertsToInteger : 2609 case ConvertsToDecimal : 2610 case ConvertsToDateTime : 2611 case ConvertsToTime : 2612 case ConvertsToBoolean : { 2613 checkContextPrimitive(focus, exp.getFunction().toCode(), false); 2614 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2615 } 2616 case ConformsTo: { 2617 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 2618 return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 2619 } 2620 case Custom : { 2621 return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); 2622 } 2623 default: 2624 break; 2625 } 2626 throw new Error("not Implemented yet"); 2627 } 2628 2629 2630 private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 2631 int i = 0; 2632 for (TypeDetails pt : typeSet) { 2633 if (i == paramTypes.size()) 2634 return; 2635 TypeDetails actual = paramTypes.get(i); 2636 i++; 2637 for (String a : actual.getTypes()) { 2638 if (!pt.hasType(worker, a)) 2639 throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 2640 } 2641 } 2642 } 2643 2644 private void checkOrdered(TypeDetails focus, String name) throws PathEngineException { 2645 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) 2646 throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 2647 } 2648 2649 private void checkContextReference(TypeDetails focus, String name) throws PathEngineException { 2650 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) 2651 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, canonical, Reference"); 2652 } 2653 2654 2655 private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException { 2656 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) 2657 throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept"); 2658 } 2659 2660 2661 private void checkContextString(TypeDetails focus, String name) throws PathEngineException { 2662 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) 2663 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 2664 } 2665 2666 2667 private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty) throws PathEngineException { 2668 if (canQty) { 2669 if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) 2670 throw new PathEngineException("The function '"+name+"'() can only be used on a Quantity or on "+primitiveTypes.toString()); 2671 } else if (!focus.hasType(primitiveTypes)) 2672 throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()); 2673 } 2674 2675 2676 private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException { 2677 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 2678 for (String f : focus.getTypes()) 2679 getChildTypesByName(f, mask, result); 2680 return result; 2681 } 2682 2683 private TypeDetails anything(CollectionStatus status) { 2684 return new TypeDetails(status, allTypes.keySet()); 2685 } 2686 2687 // private boolean isPrimitiveType(String s) { 2688 // 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"); 2689 // } 2690 2691 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2692 switch (exp.getFunction()) { 2693 case Empty : return funcEmpty(context, focus, exp); 2694 case Not : return funcNot(context, focus, exp); 2695 case Exists : return funcExists(context, focus, exp); 2696 case SubsetOf : return funcSubsetOf(context, focus, exp); 2697 case SupersetOf : return funcSupersetOf(context, focus, exp); 2698 case IsDistinct : return funcIsDistinct(context, focus, exp); 2699 case Distinct : return funcDistinct(context, focus, exp); 2700 case Count : return funcCount(context, focus, exp); 2701 case Where : return funcWhere(context, focus, exp); 2702 case Select : return funcSelect(context, focus, exp); 2703 case All : return funcAll(context, focus, exp); 2704 case Repeat : return funcRepeat(context, focus, exp); 2705 case Aggregate : return funcAggregate(context, focus, exp); 2706 case Item : return funcItem(context, focus, exp); 2707 case As : return funcAs(context, focus, exp); 2708 case OfType : return funcAs(context, focus, exp); 2709 case Type : return funcType(context, focus, exp); 2710 case Is : return funcIs(context, focus, exp); 2711 case Single : return funcSingle(context, focus, exp); 2712 case First : return funcFirst(context, focus, exp); 2713 case Last : return funcLast(context, focus, exp); 2714 case Tail : return funcTail(context, focus, exp); 2715 case Skip : return funcSkip(context, focus, exp); 2716 case Take : return funcTake(context, focus, exp); 2717 case Union : return funcUnion(context, focus, exp); 2718 case Combine : return funcCombine(context, focus, exp); 2719 case Intersect : return funcIntersect(context, focus, exp); 2720 case Exclude : return funcExclude(context, focus, exp); 2721 case Iif : return funcIif(context, focus, exp); 2722 case Lower : return funcLower(context, focus, exp); 2723 case Upper : return funcUpper(context, focus, exp); 2724 case ToChars : return funcToChars(context, focus, exp); 2725 case Substring : return funcSubstring(context, focus, exp); 2726 case StartsWith : return funcStartsWith(context, focus, exp); 2727 case EndsWith : return funcEndsWith(context, focus, exp); 2728 case Matches : return funcMatches(context, focus, exp); 2729 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 2730 case Contains : return funcContains(context, focus, exp); 2731 case Replace : return funcReplace(context, focus, exp); 2732 case Length : return funcLength(context, focus, exp); 2733 case Children : return funcChildren(context, focus, exp); 2734 case Descendants : return funcDescendants(context, focus, exp); 2735 case MemberOf : return funcMemberOf(context, focus, exp); 2736 case Trace : return funcTrace(context, focus, exp); 2737 case Today : return funcToday(context, focus, exp); 2738 case Now : return funcNow(context, focus, exp); 2739 case Resolve : return funcResolve(context, focus, exp); 2740 case Extension : return funcExtension(context, focus, exp); 2741 case AnyFalse: return funcAnyFalse(context, focus, exp); 2742 case AllFalse: return funcAllFalse(context, focus, exp); 2743 case AnyTrue: return funcAnyTrue(context, focus, exp); 2744 case AllTrue: return funcAllTrue(context, focus, exp); 2745 case HasValue : return funcHasValue(context, focus, exp); 2746 case AliasAs : return funcAliasAs(context, focus, exp); 2747 case Alias : return funcAlias(context, focus, exp); 2748 case HtmlChecks : return funcHtmlChecks(context, focus, exp); 2749 case ToInteger : return funcToInteger(context, focus, exp); 2750 case ToDecimal : return funcToDecimal(context, focus, exp); 2751 case ToString : return funcToString(context, focus, exp); 2752 case ToBoolean : return funcToBoolean(context, focus, exp); 2753 case ToQuantity : return funcToQuantity(context, focus, exp); 2754 case ToDateTime : return funcToDateTime(context, focus, exp); 2755 case ToTime : return funcToTime(context, focus, exp); 2756 case ConvertsToInteger : return funcIsInteger(context, focus, exp); 2757 case ConvertsToDecimal : return funcIsDecimal(context, focus, exp); 2758 case ConvertsToString : return funcIsString(context, focus, exp); 2759 case ConvertsToBoolean : return funcIsBoolean(context, focus, exp); 2760 case ConvertsToQuantity : return funcIsQuantity(context, focus, exp); 2761 case ConvertsToDateTime : return funcIsDateTime(context, focus, exp); 2762 case ConvertsToTime : return funcIsTime(context, focus, exp); 2763 case ConformsTo : return funcConformsTo(context, focus, exp); 2764 case Custom: { 2765 List<List<Base>> params = new ArrayList<List<Base>>(); 2766 for (ExpressionNode p : exp.getParameters()) 2767 params.add(execute(context, focus, p, true)); 2768 return hostServices.executeFunction(context.appInfo, exp.getName(), params); 2769 } 2770 default: 2771 throw new Error("not Implemented yet"); 2772 } 2773 } 2774 2775 private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2776 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2777 String name = nl.get(0).primitiveValue(); 2778 context.addAlias(name, focus); 2779 return focus; 2780 } 2781 2782 private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2783 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2784 String name = nl.get(0).primitiveValue(); 2785 List<Base> res = new ArrayList<Base>(); 2786 Base b = context.getAlias(name); 2787 if (b != null) 2788 res.add(b); 2789 return res; 2790 } 2791 2792 private List<Base> funcHtmlChecks(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2793 // todo: actually check the HTML 2794 return makeBoolean(true); 2795 } 2796 2797 2798 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2799 List<Base> result = new ArrayList<Base>(); 2800 if (exp.getParameters().size() == 1) { 2801 List<Base> pc = new ArrayList<Base>(); 2802 boolean all = true; 2803 for (Base item : focus) { 2804 pc.clear(); 2805 pc.add(item); 2806 Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 2807 if (eq != Equality.True) { 2808 all = false; 2809 break; 2810 } 2811 } 2812 result.add(new BooleanType(all).noExtensions()); 2813 } else {// (exp.getParameters().size() == 0) { 2814 boolean all = true; 2815 for (Base item : focus) { 2816 Equality eq = asBool(item); 2817 if (eq != Equality.True) { 2818 all = false; 2819 break; 2820 } 2821 } 2822 result.add(new BooleanType(all).noExtensions()); 2823 } 2824 return result; 2825 } 2826 2827 2828 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 2829 return new ExecutionContext(context.appInfo, context.resource, context.context, context.aliases, newThis); 2830 } 2831 2832 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 2833 return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 2834 } 2835 2836 2837 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2838 List<Base> result = new ArrayList<Base>(); 2839 result.add(DateTimeType.now()); 2840 return result; 2841 } 2842 2843 2844 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2845 List<Base> result = new ArrayList<Base>(); 2846 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 2847 return result; 2848 } 2849 2850 2851 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2852 throw new Error("not Implemented yet"); 2853 } 2854 2855 2856 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2857 List<Base> result = new ArrayList<Base>(); 2858 List<Base> current = new ArrayList<Base>(); 2859 current.addAll(focus); 2860 List<Base> added = new ArrayList<Base>(); 2861 boolean more = true; 2862 while (more) { 2863 added.clear(); 2864 for (Base item : current) { 2865 getChildrenByName(item, "*", added); 2866 } 2867 more = !added.isEmpty(); 2868 result.addAll(added); 2869 current.clear(); 2870 current.addAll(added); 2871 } 2872 return result; 2873 } 2874 2875 2876 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2877 List<Base> result = new ArrayList<Base>(); 2878 for (Base b : focus) 2879 getChildrenByName(b, "*", result); 2880 return result; 2881 } 2882 2883 2884 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException { 2885 List<Base> result = new ArrayList<Base>(); 2886 2887 if (focus.size() == 1) { 2888 String f = convertToString(focus.get(0)); 2889 2890 if (!Utilities.noString(f)) { 2891 2892 if (exp.getParameters().size() != 2) { 2893 2894 String t = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2895 String r = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2896 2897 String n = f.replace(t, r); 2898 result.add(new StringType(n)); 2899 } 2900 else { 2901 throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size())); 2902 } 2903 } 2904 else { 2905 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item")); 2906 } 2907 } 2908 else { 2909 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size())); 2910 } 2911 return result; 2912 } 2913 2914 2915 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2916 List<Base> result = new ArrayList<Base>(); 2917 String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2918 String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2919 2920 if (focus.size() == 1 && !Utilities.noString(regex)) 2921 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions()); 2922 else 2923 result.add(new StringType(convertToString(focus.get(0))).noExtensions()); 2924 return result; 2925 } 2926 2927 2928 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2929 List<Base> result = new ArrayList<Base>(); 2930 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2931 2932 if (focus.size() == 0) 2933 result.add(new BooleanType(false).noExtensions()); 2934 else if (Utilities.noString(sw)) 2935 result.add(new BooleanType(true).noExtensions()); 2936 else { 2937 if (focus.size() == 1 && !Utilities.noString(sw)) 2938 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions()); 2939 else 2940 result.add(new BooleanType(false).noExtensions()); 2941 } 2942 return result; 2943 } 2944 2945 2946 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2947 List<Base> result = new ArrayList<Base>(); 2948 result.add(new StringType(convertToString(focus)).noExtensions()); 2949 return result; 2950 } 2951 2952 private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2953 List<Base> result = new ArrayList<Base>(); 2954 if (focus.size() == 1) { 2955 if (focus.get(0) instanceof BooleanType) 2956 result.add(focus.get(0)); 2957 else if (focus.get(0) instanceof IntegerType) { 2958 int i = Integer.parseInt(focus.get(0).primitiveValue()); 2959 if (i == 0) 2960 result.add(new BooleanType(false).noExtensions()); 2961 else if (i == 1) 2962 result.add(new BooleanType(true).noExtensions()); 2963 } else if (focus.get(0) instanceof DecimalType) { 2964 if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) 2965 result.add(new BooleanType(false).noExtensions()); 2966 else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) 2967 result.add(new BooleanType(true).noExtensions()); 2968 } else if (focus.get(0) instanceof StringType) { 2969 if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) 2970 result.add(new BooleanType(true).noExtensions()); 2971 else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) 2972 result.add(new BooleanType(false).noExtensions()); 2973 } 2974 } 2975 return result; 2976 } 2977 2978 private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2979 List<Base> result = new ArrayList<Base>(); 2980 if (focus.size() == 1) { 2981 if (focus.get(0) instanceof Quantity) 2982 result.add(focus.get(0)); 2983 else if (focus.get(0) instanceof StringType) { 2984 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 2985 if (q != null) 2986 result.add(q.noExtensions()); 2987 } else if (focus.get(0) instanceof IntegerType) { 2988 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2989 } else if (focus.get(0) instanceof DecimalType) { 2990 result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions()); 2991 } 2992 } 2993 return result; 2994 } 2995 2996 private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2997// List<Base> result = new ArrayList<Base>(); 2998// result.add(new BooleanType(convertToBoolean(focus))); 2999// return result; 3000 throw new NotImplementedException("funcToDateTime is not implemented"); 3001} 3002 3003 private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3004// List<Base> result = new ArrayList<Base>(); 3005// result.add(new BooleanType(convertToBoolean(focus))); 3006// return result; 3007 throw new NotImplementedException("funcToTime is not implemented"); 3008} 3009 3010 3011 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3012 String s = convertToString(focus); 3013 List<Base> result = new ArrayList<Base>(); 3014 if (Utilities.isDecimal(s)) 3015 result.add(new DecimalType(s).noExtensions()); 3016 if ("true".equals(s)) 3017 result.add(new DecimalType(1).noExtensions()); 3018 if ("false".equals(s)) 3019 result.add(new DecimalType(0).noExtensions()); 3020 return result; 3021 } 3022 3023 3024 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3025 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3026 Equality v = asBool(n1); 3027 3028 if (v == Equality.True) 3029 return execute(context, focus, exp.getParameters().get(1), true); 3030 else if (exp.getParameters().size() < 3) 3031 return new ArrayList<Base>(); 3032 else 3033 return execute(context, focus, exp.getParameters().get(2), true); 3034 } 3035 3036 3037 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3038 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3039 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3040 3041 List<Base> result = new ArrayList<Base>(); 3042 for (int i = 0; i < Math.min(focus.size(), i1); i++) 3043 result.add(focus.get(i)); 3044 return result; 3045 } 3046 3047 3048 private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3049 List<Base> result = new ArrayList<Base>(); 3050 for (Base item : focus) { 3051 if (!doContains(result, item)) 3052 result.add(item); 3053 } 3054 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 3055 if (!doContains(result, item)) 3056 result.add(item); 3057 } 3058 return result; 3059 } 3060 3061 private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3062 List<Base> result = new ArrayList<Base>(); 3063 for (Base item : focus) { 3064 result.add(item); 3065 } 3066 for (Base item : execute(context, focus, exp.getParameters().get(0), true)) { 3067 result.add(item); 3068 } 3069 return result; 3070 } 3071 3072 private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3073 List<Base> result = new ArrayList<Base>(); 3074 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 3075 3076 for (Base item : focus) { 3077 if (!doContains(result, item) && doContains(other, item)) 3078 result.add(item); 3079 } 3080 return result; 3081 } 3082 3083 private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3084 List<Base> result = new ArrayList<Base>(); 3085 List<Base> other = execute(context, focus, exp.getParameters().get(0), true); 3086 3087 for (Base item : focus) { 3088 if (!doContains(other, item)) 3089 result.add(item); 3090 } 3091 return result; 3092 } 3093 3094 3095 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3096 if (focus.size() == 1) 3097 return focus; 3098 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 3099 } 3100 3101 3102 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3103 if (focus.size() == 0 || focus.size() > 1) 3104 return makeBoolean(false); 3105 String ns = null; 3106 String n = null; 3107 3108 ExpressionNode texp = exp.getParameters().get(0); 3109 if (texp.getKind() != Kind.Name) 3110 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 3111 if (texp.getInner() != null) { 3112 if (texp.getInner().getKind() != Kind.Name) 3113 throw new PathEngineException("Unsupported Expression type for Parameter on Is"); 3114 ns = texp.getName(); 3115 n = texp.getInner().getName(); 3116 } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) { 3117 ns = "System"; 3118 n = texp.getName(); 3119 } else { 3120 ns = "FHIR"; 3121 n = texp.getName(); 3122 } 3123 if (ns.equals("System")) { 3124 if (focus.get(0) instanceof Resource) 3125 return makeBoolean(false); 3126 if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) 3127 return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType()))); 3128 else 3129 return makeBoolean(false); 3130 } else if (ns.equals("FHIR")) { 3131 return makeBoolean(n.equals(focus.get(0).fhirType())); 3132 } else { 3133 return makeBoolean(false); 3134 } 3135 } 3136 3137 3138 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3139 List<Base> result = new ArrayList<Base>(); 3140 String tn; 3141 if (exp.getParameters().get(0).getInner() != null) 3142 tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName(); 3143 else 3144 tn = "FHIR."+exp.getParameters().get(0).getName(); 3145 for (Base b : focus) { 3146 if (tn.startsWith("System.")) { 3147 if (b instanceof Element &&((Element) b).isDisallowExtensions()) 3148 if (b.hasType(tn.substring(7))) 3149 result.add(b); 3150 } else if (tn.startsWith("FHIR.")) { 3151 if (b.hasType(tn.substring(5))) 3152 result.add(b); 3153 } 3154 } 3155 return result; 3156 } 3157 3158 private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3159 List<Base> result = new ArrayList<Base>(); 3160 for (Base item : focus) 3161 result.add(new ClassTypeInfo(item)); 3162 return result; 3163 } 3164 3165 3166 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3167 List<Base> result = new ArrayList<Base>(); 3168 List<Base> current = new ArrayList<Base>(); 3169 current.addAll(focus); 3170 List<Base> added = new ArrayList<Base>(); 3171 boolean more = true; 3172 while (more) { 3173 added.clear(); 3174 List<Base> pc = new ArrayList<Base>(); 3175 for (Base item : current) { 3176 pc.clear(); 3177 pc.add(item); 3178 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 3179 } 3180 more = !added.isEmpty(); 3181 result.addAll(added); 3182 current.clear(); 3183 current.addAll(added); 3184 } 3185 return result; 3186 } 3187 3188 3189 private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3190 List<Base> total = new ArrayList<Base>(); 3191 if (exp.parameterCount() > 1) 3192 total = execute(context, focus, exp.getParameters().get(1), false); 3193 3194 List<Base> pc = new ArrayList<Base>(); 3195 for (Base item : focus) { 3196 ExecutionContext c = changeThis(context, item); 3197 c.total = total; 3198 total = execute(c, pc, exp.getParameters().get(0), true); 3199 } 3200 return total; 3201 } 3202 3203 3204 3205 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3206 if (focus.size() < 1) 3207 return makeBoolean(true); 3208 if (focus.size() == 1) 3209 return makeBoolean(true); 3210 3211 boolean distinct = true; 3212 for (int i = 0; i < focus.size(); i++) { 3213 for (int j = i+1; j < focus.size(); j++) { 3214 Boolean eq = doEquals(focus.get(j), focus.get(i)); 3215 if (eq == null) { 3216 return new ArrayList<Base>(); 3217 } else if (eq == true) { 3218 distinct = false; 3219 break; 3220 } 3221 } 3222 } 3223 return makeBoolean(distinct); 3224 } 3225 3226 3227 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3228 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 3229 3230 boolean valid = true; 3231 for (Base item : target) { 3232 boolean found = false; 3233 for (Base t : focus) { 3234 if (Base.compareDeep(item, t, false)) { 3235 found = true; 3236 break; 3237 } 3238 } 3239 if (!found) { 3240 valid = false; 3241 break; 3242 } 3243 } 3244 List<Base> result = new ArrayList<Base>(); 3245 result.add(new BooleanType(valid).noExtensions()); 3246 return result; 3247 } 3248 3249 3250 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3251 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 3252 3253 boolean valid = true; 3254 for (Base item : focus) { 3255 boolean found = false; 3256 for (Base t : target) { 3257 if (Base.compareDeep(item, t, false)) { 3258 found = true; 3259 break; 3260 } 3261 } 3262 if (!found) { 3263 valid = false; 3264 break; 3265 } 3266 } 3267 List<Base> result = new ArrayList<Base>(); 3268 result.add(new BooleanType(valid).noExtensions()); 3269 return result; 3270 } 3271 3272 3273 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3274 List<Base> result = new ArrayList<Base>(); 3275 boolean empty = true; 3276 for (Base f : focus) 3277 if (!f.isEmpty()) 3278 empty = false; 3279 result.add(new BooleanType(!empty).noExtensions()); 3280 return result; 3281 } 3282 3283 3284 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3285 List<Base> result = new ArrayList<Base>(); 3286 for (Base item : focus) { 3287 String s = convertToString(item); 3288 if (item.fhirType().equals("Reference")) { 3289 Property p = item.getChildByName("reference"); 3290 if (p != null && p.hasValues()) 3291 s = convertToString(p.getValues().get(0)); 3292 else 3293 s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it) 3294 } 3295 if (item.fhirType().equals("canonical")) { 3296 s = item.primitiveValue(); 3297 } 3298 if (s != null) { 3299 Base res = null; 3300 if (s.startsWith("#")) { 3301 Property p = context.resource.getChildByName("contained"); 3302 for (Base c : p.getValues()) { 3303 if (chompHash(s).equals(chompHash(c.getIdBase()))) { 3304 res = c; 3305 break; 3306 } 3307 } 3308 } else if (hostServices != null) { 3309 res = hostServices.resolveReference(context.appInfo, s); 3310 } 3311 if (res != null) 3312 result.add(res); 3313 } 3314 } 3315 3316 return result; 3317 } 3318 3319 /** 3320 * Strips a leading hashmark (#) if present at the start of a string 3321 */ 3322 private String chompHash(String theId) { 3323 String retVal = theId; 3324 while (retVal.startsWith("#")) { 3325 retVal = retVal.substring(1); 3326 } 3327 return retVal; 3328 } 3329 3330 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3331 List<Base> result = new ArrayList<Base>(); 3332 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 3333 String url = nl.get(0).primitiveValue(); 3334 3335 for (Base item : focus) { 3336 List<Base> ext = new ArrayList<Base>(); 3337 getChildrenByName(item, "extension", ext); 3338 getChildrenByName(item, "modifierExtension", ext); 3339 for (Base ex : ext) { 3340 List<Base> vl = new ArrayList<Base>(); 3341 getChildrenByName(ex, "url", vl); 3342 if (convertToString(vl).equals(url)) 3343 result.add(ex); 3344 } 3345 } 3346 return result; 3347 } 3348 3349 private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3350 List<Base> result = new ArrayList<Base>(); 3351 if (exp.getParameters().size() == 1) { 3352 boolean all = true; 3353 List<Base> pc = new ArrayList<Base>(); 3354 for (Base item : focus) { 3355 pc.clear(); 3356 pc.add(item); 3357 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3358 Equality v = asBool(res); 3359 if (v != Equality.False) { 3360 all = false; 3361 break; 3362 } 3363 } 3364 result.add(new BooleanType(all).noExtensions()); 3365 } else { 3366 boolean all = true; 3367 for (Base item : focus) { 3368 Equality v = asBool(item); 3369 if (v != Equality.False) { 3370 all = false; 3371 break; 3372 } 3373 } 3374 result.add(new BooleanType(all).noExtensions()); 3375 } 3376 return result; 3377 } 3378 3379 private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3380 List<Base> result = new ArrayList<Base>(); 3381 if (exp.getParameters().size() == 1) { 3382 boolean any = false; 3383 List<Base> pc = new ArrayList<Base>(); 3384 for (Base item : focus) { 3385 pc.clear(); 3386 pc.add(item); 3387 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3388 Equality v = asBool(res); 3389 if (v == Equality.False) { 3390 any = true; 3391 break; 3392 } 3393 } 3394 result.add(new BooleanType(any).noExtensions()); 3395 } else { 3396 boolean any = false; 3397 for (Base item : focus) { 3398 Equality v = asBool(item); 3399 if (v == Equality.False) { 3400 any = true; 3401 break; 3402 } 3403 } 3404 result.add(new BooleanType(any).noExtensions()); 3405 } 3406 return result; 3407 } 3408 3409 private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3410 List<Base> result = new ArrayList<Base>(); 3411 if (exp.getParameters().size() == 1) { 3412 boolean all = true; 3413 List<Base> pc = new ArrayList<Base>(); 3414 for (Base item : focus) { 3415 pc.clear(); 3416 pc.add(item); 3417 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3418 Equality v = asBool(res); 3419 if (v != Equality.True) { 3420 all = false; 3421 break; 3422 } 3423 } 3424 result.add(new BooleanType(all).noExtensions()); 3425 } else { 3426 boolean all = true; 3427 for (Base item : focus) { 3428 Equality v = asBool(item); 3429 if (v != Equality.True) { 3430 all = false; 3431 break; 3432 } 3433 } 3434 result.add(new BooleanType(all).noExtensions()); 3435 } 3436 return result; 3437 } 3438 3439 private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3440 List<Base> result = new ArrayList<Base>(); 3441 if (exp.getParameters().size() == 1) { 3442 boolean any = false; 3443 List<Base> pc = new ArrayList<Base>(); 3444 for (Base item : focus) { 3445 pc.clear(); 3446 pc.add(item); 3447 List<Base> res = execute(context, pc, exp.getParameters().get(0), true); 3448 Equality v = asBool(res); 3449 if (v == Equality.True) { 3450 any = true; 3451 break; 3452 } 3453 } 3454 result.add(new BooleanType(any).noExtensions()); 3455 } else { 3456 boolean any = false; 3457 for (Base item : focus) { 3458 Equality v = asBool(item); 3459 if (v == Equality.True) { 3460 any = true; 3461 break; 3462 } 3463 } 3464 result.add(new BooleanType(any).noExtensions()); 3465 } 3466 return result; 3467 } 3468 3469 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3470 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 3471 String name = nl.get(0).primitiveValue(); 3472 if (exp.getParameters().size() == 2) { 3473 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 3474 log(name, n2); 3475 } else 3476 log(name, focus); 3477 return focus; 3478 } 3479 3480 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3481 if (focus.size() <= 1) 3482 return focus; 3483 3484 List<Base> result = new ArrayList<Base>(); 3485 for (int i = 0; i < focus.size(); i++) { 3486 boolean found = false; 3487 for (int j = i+1; j < focus.size(); j++) { 3488 Boolean eq = doEquals(focus.get(j), focus.get(i)); 3489 if (eq == null) 3490 return new ArrayList<Base>(); 3491 else if (eq == true) { 3492 found = true; 3493 break; 3494 } 3495 } 3496 if (!found) 3497 result.add(focus.get(i)); 3498 } 3499 return result; 3500 } 3501 3502 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3503 List<Base> result = new ArrayList<Base>(); 3504 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3505 3506 if (focus.size() == 1 && !Utilities.noString(sw)) { 3507 String st = convertToString(focus.get(0)); 3508 if (Utilities.noString(st)) 3509 result.add(new BooleanType(false).noExtensions()); 3510 else 3511 result.add(new BooleanType(st.matches(sw)).noExtensions()); 3512 } else 3513 result.add(new BooleanType(false).noExtensions()); 3514 return result; 3515 } 3516 3517 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3518 List<Base> result = new ArrayList<Base>(); 3519 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3520 3521 if (focus.size() != 1) { 3522 result.add(new BooleanType(false).noExtensions()); 3523 } else if (Utilities.noString(sw)) { 3524 result.add(new BooleanType(true).noExtensions()); 3525 } else { 3526 String st = convertToString(focus.get(0)); 3527 if (Utilities.noString(st)) 3528 result.add(new BooleanType(false).noExtensions()); 3529 else 3530 result.add(new BooleanType(st.contains(sw)).noExtensions()); 3531 } 3532 return result; 3533 } 3534 3535 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3536 List<Base> result = new ArrayList<Base>(); 3537 if (focus.size() == 1) { 3538 String s = convertToString(focus.get(0)); 3539 result.add(new IntegerType(s.length()).noExtensions()); 3540 } 3541 return result; 3542 } 3543 3544 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3545 List<Base> result = new ArrayList<Base>(); 3546 if (focus.size() == 1) { 3547 String s = convertToString(focus.get(0)); 3548 result.add(new BooleanType(!Utilities.noString(s)).noExtensions()); 3549 } else 3550 result.add(new BooleanType(false).noExtensions()); 3551 return result; 3552 } 3553 3554 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3555 List<Base> result = new ArrayList<Base>(); 3556 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3557 3558 if (focus.size() == 0) { 3559 result.add(new BooleanType(false).noExtensions()); 3560 } else if (Utilities.noString(sw)) { 3561 result.add(new BooleanType(true).noExtensions()); 3562 } else { 3563 String s = convertToString(focus.get(0)); 3564 if (s == null) 3565 result.add(new BooleanType(false).noExtensions()); 3566 else 3567 result.add(new BooleanType(s.startsWith(sw)).noExtensions()); 3568 } 3569 return result; 3570 } 3571 3572 private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3573 List<Base> result = new ArrayList<Base>(); 3574 if (focus.size() == 1) { 3575 String s = convertToString(focus.get(0)); 3576 if (!Utilities.noString(s)) 3577 result.add(new StringType(s.toLowerCase()).noExtensions()); 3578 } 3579 return result; 3580 } 3581 3582 private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3583 List<Base> result = new ArrayList<Base>(); 3584 if (focus.size() == 1) { 3585 String s = convertToString(focus.get(0)); 3586 if (!Utilities.noString(s)) 3587 result.add(new StringType(s.toUpperCase()).noExtensions()); 3588 } 3589 return result; 3590 } 3591 3592 private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3593 List<Base> result = new ArrayList<Base>(); 3594 if (focus.size() == 1) { 3595 String s = convertToString(focus.get(0)); 3596 for (char c : s.toCharArray()) 3597 result.add(new StringType(String.valueOf(c)).noExtensions()); 3598 } 3599 return result; 3600 } 3601 3602 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3603 List<Base> result = new ArrayList<Base>(); 3604 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3605 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3606 int i2 = -1; 3607 if (exp.parameterCount() == 2) { 3608 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 3609 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 3610 } 3611 3612 if (focus.size() == 1) { 3613 String sw = convertToString(focus.get(0)); 3614 String s; 3615 if (i1 < 0 || i1 >= sw.length()) 3616 return new ArrayList<Base>(); 3617 if (exp.parameterCount() == 2) 3618 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 3619 else 3620 s = sw.substring(i1); 3621 if (!Utilities.noString(s)) 3622 result.add(new StringType(s).noExtensions()); 3623 } 3624 return result; 3625 } 3626 3627 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3628 String s = convertToString(focus); 3629 List<Base> result = new ArrayList<Base>(); 3630 if (Utilities.isInteger(s)) 3631 result.add(new IntegerType(s).noExtensions()); 3632 else if ("true".equals(s)) 3633 result.add(new IntegerType(1).noExtensions()); 3634 else if ("false".equals(s)) 3635 result.add(new IntegerType(0).noExtensions()); 3636 return result; 3637 } 3638 3639 private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3640 List<Base> result = new ArrayList<Base>(); 3641 if (focus.size() != 1) 3642 result.add(new BooleanType(false).noExtensions()); 3643 else if (focus.get(0) instanceof IntegerType) 3644 result.add(new BooleanType(true).noExtensions()); 3645 else if (focus.get(0) instanceof BooleanType) 3646 result.add(new BooleanType(true).noExtensions()); 3647 else if (focus.get(0) instanceof StringType) 3648 result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions()); 3649 else 3650 result.add(new BooleanType(false).noExtensions()); 3651 return result; 3652 } 3653 3654 private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3655 List<Base> result = new ArrayList<Base>(); 3656 if (focus.size() != 1) 3657 result.add(new BooleanType(false).noExtensions()); 3658 else if (focus.get(0) instanceof IntegerType) 3659 result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions()); 3660 else if (focus.get(0) instanceof DecimalType) 3661 result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions()); 3662 else if (focus.get(0) instanceof BooleanType) 3663 result.add(new BooleanType(true).noExtensions()); 3664 else if (focus.get(0) instanceof StringType) 3665 result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions()); 3666 else 3667 result.add(new BooleanType(false).noExtensions()); 3668 return result; 3669 } 3670 3671 private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3672 List<Base> result = new ArrayList<Base>(); 3673 if (focus.size() != 1) 3674 result.add(new BooleanType(false).noExtensions()); 3675 else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) 3676 result.add(new BooleanType(true).noExtensions()); 3677 else if (focus.get(0) instanceof StringType) 3678 result.add(new BooleanType((convertToString(focus.get(0)).matches 3679 ("([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()); 3680 else 3681 result.add(new BooleanType(false).noExtensions()); 3682 return result; 3683 } 3684 3685 private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3686 if (hostServices == null) 3687 throw new FHIRException("Unable to check conformsTo - no hostservices provided"); 3688 List<Base> result = new ArrayList<Base>(); 3689 if (focus.size() != 1) 3690 result.add(new BooleanType(false).noExtensions()); 3691 else { 3692 String url = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3693 result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo, focus.get(0), url)).noExtensions()); 3694 } 3695 return result; 3696 } 3697 3698 private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3699 List<Base> result = new ArrayList<Base>(); 3700 if (focus.size() != 1) 3701 result.add(new BooleanType(false).noExtensions()); 3702 else if (focus.get(0) instanceof TimeType) 3703 result.add(new BooleanType(true).noExtensions()); 3704 else if (focus.get(0) instanceof StringType) 3705 result.add(new BooleanType((convertToString(focus.get(0)).matches 3706 ("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()); 3707 else 3708 result.add(new BooleanType(false).noExtensions()); 3709 return result; 3710 } 3711 3712 private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3713 List<Base> result = new ArrayList<Base>(); 3714 if (focus.size() != 1) 3715 result.add(new BooleanType(false).noExtensions()); 3716 else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) 3717 result.add(new BooleanType(true).noExtensions()); 3718 else 3719 result.add(new BooleanType(false).noExtensions()); 3720 return result; 3721 } 3722 3723 private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3724 List<Base> result = new ArrayList<Base>(); 3725 if (focus.size() != 1) 3726 result.add(new BooleanType(false).noExtensions()); 3727 else if (focus.get(0) instanceof IntegerType) 3728 result.add(new BooleanType(true).noExtensions()); 3729 else if (focus.get(0) instanceof DecimalType) 3730 result.add(new BooleanType(true).noExtensions()); 3731 else if (focus.get(0) instanceof Quantity) 3732 result.add(new BooleanType(true).noExtensions()); 3733 else if (focus.get(0) instanceof BooleanType) 3734 result.add(new BooleanType(true).noExtensions()); 3735 else if (focus.get(0) instanceof StringType) { 3736 Quantity q = parseQuantityString(focus.get(0).primitiveValue()); 3737 result.add(new BooleanType(q != null).noExtensions()); 3738 } else 3739 result.add(new BooleanType(false).noExtensions()); 3740 return result; 3741 } 3742 3743 public Quantity parseQuantityString(String s) { 3744 if (s == null) 3745 return null; 3746 s = s.trim(); 3747 if (s.contains(" ")) { 3748 String v = s.substring(0, s.indexOf(" ")).trim(); 3749 s = s.substring(s.indexOf(" ")).trim(); 3750 if (!Utilities.isDecimal(v)) 3751 return null; 3752 if (s.startsWith("'") && s.endsWith("'")) 3753 return Quantity.fromUcum(v, s.substring(1, s.length()-1)); 3754 if (s.equals("year") || s.equals("years")) 3755 return Quantity.fromUcum(v, "a"); 3756 else if (s.equals("month") || s.equals("months")) 3757 return Quantity.fromUcum(v, "mo"); 3758 else if (s.equals("week") || s.equals("weeks")) 3759 return Quantity.fromUcum(v, "wk"); 3760 else if (s.equals("day") || s.equals("days")) 3761 return Quantity.fromUcum(v, "d"); 3762 else if (s.equals("hour") || s.equals("hours")) 3763 return Quantity.fromUcum(v, "h"); 3764 else if (s.equals("minute") || s.equals("minutes")) 3765 return Quantity.fromUcum(v, "min"); 3766 else if (s.equals("second") || s.equals("seconds")) 3767 return Quantity.fromUcum(v, "s"); 3768 else if (s.equals("millisecond") || s.equals("milliseconds")) 3769 return Quantity.fromUcum(v, "ms"); 3770 else 3771 return null; 3772 } else { 3773 if (Utilities.isDecimal(s)) 3774 return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1"); 3775 else 3776 return null; 3777 } 3778 } 3779 3780 3781 private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3782 List<Base> result = new ArrayList<Base>(); 3783 if (focus.size() != 1) 3784 result.add(new BooleanType(false).noExtensions()); 3785 else if (focus.get(0) instanceof IntegerType) 3786 result.add(new BooleanType(true).noExtensions()); 3787 else if (focus.get(0) instanceof BooleanType) 3788 result.add(new BooleanType(true).noExtensions()); 3789 else if (focus.get(0) instanceof DecimalType) 3790 result.add(new BooleanType(true).noExtensions()); 3791 else if (focus.get(0) instanceof StringType) 3792 result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)))).noExtensions()); 3793 else 3794 result.add(new BooleanType(false).noExtensions()); 3795 return result; 3796 } 3797 3798 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3799 List<Base> result = new ArrayList<Base>(); 3800 result.add(new IntegerType(focus.size()).noExtensions()); 3801 return result; 3802 } 3803 3804 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3805 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 3806 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 3807 3808 List<Base> result = new ArrayList<Base>(); 3809 for (int i = i1; i < focus.size(); i++) 3810 result.add(focus.get(i)); 3811 return result; 3812 } 3813 3814 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3815 List<Base> result = new ArrayList<Base>(); 3816 for (int i = 1; i < focus.size(); i++) 3817 result.add(focus.get(i)); 3818 return result; 3819 } 3820 3821 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3822 List<Base> result = new ArrayList<Base>(); 3823 if (focus.size() > 0) 3824 result.add(focus.get(focus.size()-1)); 3825 return result; 3826 } 3827 3828 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3829 List<Base> result = new ArrayList<Base>(); 3830 if (focus.size() > 0) 3831 result.add(focus.get(0)); 3832 return result; 3833 } 3834 3835 3836 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3837 List<Base> result = new ArrayList<Base>(); 3838 List<Base> pc = new ArrayList<Base>(); 3839 for (Base item : focus) { 3840 pc.clear(); 3841 pc.add(item); 3842 Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 3843 if (v == Equality.True) 3844 result.add(item); 3845 } 3846 return result; 3847 } 3848 3849 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3850 List<Base> result = new ArrayList<Base>(); 3851 List<Base> pc = new ArrayList<Base>(); 3852 for (Base item : focus) { 3853 pc.clear(); 3854 pc.add(item); 3855 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 3856 } 3857 return result; 3858 } 3859 3860 3861 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 3862 List<Base> result = new ArrayList<Base>(); 3863 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 3864 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 3865 result.add(focus.get(Integer.parseInt(s))); 3866 return result; 3867 } 3868 3869 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 3870 List<Base> result = new ArrayList<Base>(); 3871 result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions()); 3872 return result; 3873 } 3874 3875 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 3876 List<Base> result = new ArrayList<Base>(); 3877 Equality v = asBool(focus); 3878 if (v != Equality.Null) 3879 result.add(new BooleanType(v != Equality.True)); 3880 return result; 3881 } 3882 3883 public class ElementDefinitionMatch { 3884 private ElementDefinition definition; 3885 private String fixedType; 3886 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 3887 super(); 3888 this.definition = definition; 3889 this.fixedType = fixedType; 3890 } 3891 public ElementDefinition getDefinition() { 3892 return definition; 3893 } 3894 public String getFixedType() { 3895 return fixedType; 3896 } 3897 3898 } 3899 3900 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 3901 if (Utilities.noString(type)) 3902 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 3903 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) 3904 return; 3905 if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 3906 getSimpleTypeChildTypesByName(name, result); 3907 } else if (type.equals(TypeDetails.FP_ClassInfo)) { 3908 getClassInfoChildTypesByName(name, result); 3909 } else { 3910 String url = null; 3911 if (type.contains("#")) { 3912 url = type.substring(0, type.indexOf("#")); 3913 } else { 3914 url = type; 3915 } 3916 String tail = ""; 3917 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 3918 if (sd == null) 3919 throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 3920 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 3921 ElementDefinitionMatch m = null; 3922 if (type.contains("#")) 3923 m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false); 3924 if (m != null && hasDataType(m.definition)) { 3925 if (m.fixedType != null) 3926 { 3927 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs())); 3928 if (dt == null) 3929 throw new DefinitionException("unknown data type "+m.fixedType); 3930 sdl.add(dt); 3931 } else 3932 for (TypeRefComponent t : m.definition.getType()) { 3933 StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs())); 3934 if (dt == null) 3935 throw new DefinitionException("unknown data type "+t.getCode()); 3936 sdl.add(dt); 3937 } 3938 } else { 3939 sdl.add(sd); 3940 if (type.contains("#")) { 3941 tail = type.substring(type.indexOf("#")+1); 3942 tail = tail.substring(tail.indexOf(".")); 3943 } 3944 } 3945 3946 for (StructureDefinition sdi : sdl) { 3947 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 3948 if (name.equals("**")) { 3949 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 3950 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 3951 if (ed.getPath().startsWith(path)) 3952 for (TypeRefComponent t : ed.getType()) { 3953 if (t.hasCode() && t.getCodeElement().hasValue()) { 3954 String tn = null; 3955 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 3956 tn = sdi.getType()+"#"+ed.getPath(); 3957 else 3958 tn = t.getCode(); 3959 if (t.getCode().equals("Resource")) { 3960 for (String rn : worker.getResourceNames()) { 3961 if (!result.hasType(worker, rn)) { 3962 getChildTypesByName(result.addType(rn), "**", result); 3963 } 3964 } 3965 } else if (!result.hasType(worker, tn)) { 3966 getChildTypesByName(result.addType(tn), "**", result); 3967 } 3968 } 3969 } 3970 } 3971 } else if (name.equals("*")) { 3972 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 3973 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 3974 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 3975 for (TypeRefComponent t : ed.getType()) { 3976 if (Utilities.noString(t.getCode())) // Element.id or Extension.url 3977 result.addType("System.string"); 3978 else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 3979 result.addType(sdi.getType()+"#"+ed.getPath()); 3980 else if (t.getCode().equals("Resource")) 3981 result.addTypes(worker.getResourceNames()); 3982 else 3983 result.addType(t.getCode()); 3984 } 3985 } 3986 } else { 3987 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 3988 3989 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 3990 if (ed != null) { 3991 if (!Utilities.noString(ed.getFixedType())) 3992 result.addType(ed.getFixedType()); 3993 else 3994 for (TypeRefComponent t : ed.getDefinition().getType()) { 3995 if (Utilities.noString(t.getCode())) { 3996 if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url")) 3997 result.addType(TypeDetails.FP_NS, "string"); 3998 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 3999 } 4000 4001 ProfiledType pt = null; 4002 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 4003 pt = new ProfiledType(sdi.getUrl()+"#"+path); 4004 else if (t.getCode().equals("Resource")) 4005 result.addTypes(worker.getResourceNames()); 4006 else 4007 pt = new ProfiledType(t.getCode()); 4008 if (pt != null) { 4009 if (t.hasProfile()) 4010 pt.addProfiles(t.getProfile()); 4011 if (ed.getDefinition().hasBinding()) 4012 pt.addBinding(ed.getDefinition().getBinding()); 4013 result.addType(pt); 4014 } 4015 } 4016 } 4017 } 4018 } 4019 } 4020 } 4021 4022 private void getClassInfoChildTypesByName(String name, TypeDetails result) { 4023 if (name.equals("namespace")) 4024 result.addType(TypeDetails.FP_String); 4025 if (name.equals("name")) 4026 result.addType(TypeDetails.FP_String); 4027 } 4028 4029 4030 private void getSimpleTypeChildTypesByName(String name, TypeDetails result) { 4031 if (name.equals("namespace")) 4032 result.addType(TypeDetails.FP_String); 4033 if (name.equals("name")) 4034 result.addType(TypeDetails.FP_String); 4035 } 4036 4037 4038 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 4039 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4040 if (ed.getPath().equals(path)) { 4041 if (ed.hasContentReference()) { 4042 return getElementDefinitionById(sd, ed.getContentReference()); 4043 } else 4044 return new ElementDefinitionMatch(ed, null); 4045 } 4046 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) 4047 return new ElementDefinitionMatch(ed, null); 4048 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 4049 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 4050 if (primitiveTypes.contains(s)) 4051 return new ElementDefinitionMatch(ed, s); 4052 else 4053 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); 4054 } 4055 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 4056 // now we walk into the type. 4057 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 4058 throw new PathEngineException("Internal typing issue...."); 4059 StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); 4060 if (nsd == null) 4061 throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode()); 4062 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName); 4063 } 4064 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 4065 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 4066 return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName); 4067 } 4068 } 4069 return null; 4070 } 4071 4072 private boolean isAbstractType(List<TypeRefComponent> list) { 4073 return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 4074} 4075 4076 4077 private boolean hasType(ElementDefinition ed, String s) { 4078 for (TypeRefComponent t : ed.getType()) 4079 if (s.equalsIgnoreCase(t.getCode())) 4080 return true; 4081 return false; 4082 } 4083 4084 private boolean hasDataType(ElementDefinition ed) { 4085 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 4086 } 4087 4088 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 4089 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 4090 if (ref.equals("#"+ed.getId())) 4091 return new ElementDefinitionMatch(ed, null); 4092 } 4093 return null; 4094 } 4095 4096 4097 public boolean hasLog() { 4098 return log != null && log.length() > 0; 4099 } 4100 4101 4102 public String takeLog() { 4103 if (!hasLog()) 4104 return ""; 4105 String s = log.toString(); 4106 log = new StringBuilder(); 4107 return s; 4108 } 4109 4110 4111 /** given an element definition in a profile, what element contains the differentiating fixed 4112 * for the element, given the differentiating expresssion. The expression is only allowed to 4113 * use a subset of FHIRPath 4114 * 4115 * @param profile 4116 * @param element 4117 * @return 4118 * @throws PathEngineException 4119 * @throws DefinitionException 4120 */ 4121 public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException { 4122 StructureDefinition sd = profile; 4123 ElementDefinition focus = null; 4124 4125 if (expr.getKind() == Kind.Name) { 4126 List<ElementDefinition> childDefinitions; 4127 childDefinitions = ProfileUtilities.getChildMap(sd, element); 4128 // if that's empty, get the children of the type 4129 if (childDefinitions.isEmpty()) { 4130 sd = fetchStructureByType(element); 4131 if (sd == null) 4132 throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getProfile()+"' on "+element.getId()+" could not be resolved"); 4133 childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); 4134 } 4135 for (ElementDefinition t : childDefinitions) { 4136 if (tailMatches(t, expr.getName())) { 4137 focus = t; 4138 break; 4139 } 4140 } 4141 } else if (expr.getKind() == Kind.Function) { 4142 if ("resolve".equals(expr.getName())) { 4143 if (!element.hasType()) 4144 throw new DefinitionException("illegal use of resolve() in discriminator - no type on element "+element.getId()); 4145 if (element.getType().size() > 1) 4146 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on "+element.getId()); 4147 if (!element.getType().get(0).hasTarget()) 4148 throw new DefinitionException("illegal use of resolve() in discriminator - type on "+element.getId()+" is not Reference ("+element.getType().get(0).getCode()+")"); 4149 if (element.getType().get(0).getTargetProfile().size() > 1) 4150 throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on "+element.getId()); 4151 sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue()); 4152 if (sd == null) 4153 throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getTargetProfile()+"' on "+element.getId()+" could not be resolved"); 4154 focus = sd.getSnapshot().getElementFirstRep(); 4155 } else if ("extension".equals(expr.getName())) { 4156 String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 4157// targetUrl = targetUrl.substring(1,targetUrl.length()-1); 4158 List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(sd, element); 4159 for (ElementDefinition t : childDefinitions) { 4160 if (t.getPath().endsWith(".extension") && t.hasSliceName()) { 4161 sd = worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue()); 4162 while (sd!=null && !sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) 4163 sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 4164 if (sd.getUrl().equals(targetUrl)) { 4165 focus = t; 4166 break; 4167 } 4168 } 4169 } 4170 } else 4171 throw new DefinitionException("illegal function name "+expr.getName()+"() in discriminator"); 4172 } else if (expr.getKind() == Kind.Group) { 4173 throw new DefinitionException("illegal expression syntax in discriminator (group)"); 4174 } else if (expr.getKind() == Kind.Constant) { 4175 throw new DefinitionException("illegal expression syntax in discriminator (const)"); 4176 } 4177 4178 if (focus == null) 4179 throw new DefinitionException("Unable to resolve discriminator"); 4180 else if (expr.getInner() == null) 4181 return focus; 4182 else 4183 return evaluateDefinition(expr.getInner(), sd, focus); 4184 } 4185 4186 private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException { 4187 if (ed.getType().size() == 0) 4188 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, no type"); 4189 if (ed.getType().size() > 1) 4190 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple types"); 4191 if (ed.getType().get(0).getProfile().size() > 1) 4192 throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple type profiles"); 4193 if (ed.hasSlicing()) 4194 throw new DefinitionException("Error in discriminator at "+ed.getId()+": slicing found"); 4195 if (ed.getType().get(0).hasProfile()) 4196 return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); 4197 else 4198 return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs())); 4199 } 4200 4201 4202 private boolean tailMatches(ElementDefinition t, String d) { 4203 String tail = tailDot(t.getPath()); 4204 if (d.contains("[")) 4205 return tail.startsWith(d.substring(0, d.indexOf('['))); 4206 else if (tail.equals(d)) 4207 return true; 4208 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())) 4209 return tail.startsWith(d); 4210 4211 return false; 4212 } 4213 4214 private String tailDot(String path) { 4215 return path.substring(path.lastIndexOf(".") + 1); 4216 } 4217 4218 private Equality asBool(List<Base> items) throws PathEngineException { 4219 if (items.size() == 0) 4220 return Equality.Null; 4221 else if (items.size() == 1) 4222 return asBool(items.get(0)); 4223 else 4224 throw new PathEngineException("Unable to evaluate as a boolean: "+convertToString(items)); 4225 } 4226 4227 private Equality asBoolFromInt(String s) { 4228 try { 4229 int i = Integer.parseInt(s); 4230 switch (i) { 4231 case 0: return Equality.False; 4232 case 1: return Equality.True; 4233 default: return Equality.Null; 4234 } 4235 } catch (Exception e) { 4236 return Equality.Null; 4237 } 4238 } 4239 4240 private Equality asBoolFromDec(String s) { 4241 try { 4242 BigDecimal d = new BigDecimal(s); 4243 if (d.compareTo(BigDecimal.ZERO) == 0) 4244 return Equality.False; 4245 else if (d.compareTo(BigDecimal.ONE) == 0) 4246 return Equality.True; 4247 else 4248 return Equality.Null; 4249 } catch (Exception e) { 4250 return Equality.Null; 4251 } 4252 } 4253 4254 private Equality asBool(Base item) { 4255 if (item instanceof BooleanType) 4256 return boolToTriState(((BooleanType) item).booleanValue()); 4257 else if (item.isBooleanPrimitive()) { 4258 if (Utilities.existsInList(item.primitiveValue(), "true")) 4259 return Equality.True; 4260 else if (Utilities.existsInList(item.primitiveValue(), "false")) 4261 return Equality.False; 4262 else 4263 return Equality.Null; 4264 } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) 4265 return asBoolFromInt(item.primitiveValue()); 4266 else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) 4267 return asBoolFromDec(item.primitiveValue()); 4268 else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) { 4269 if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) 4270 return Equality.True; 4271 else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) 4272 return Equality.False; 4273 else if (Utilities.isInteger(item.primitiveValue())) 4274 return asBoolFromInt(item.primitiveValue()); 4275 else if (Utilities.isDecimal(item.primitiveValue())) 4276 return asBoolFromDec(item.primitiveValue()); 4277 else 4278 return Equality.Null; 4279 } 4280 return Equality.Null; 4281 } 4282 4283 private Equality boolToTriState(boolean b) { 4284 return b ? Equality.True : Equality.False; 4285 } 4286 4287}