
001package org.hl7.fhir.dstu3.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 035import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 036import ca.uhn.fhir.util.ElementUtil; 037import org.fhir.ucum.Decimal; 038import org.fhir.ucum.UcumException; 039import org.hl7.fhir.dstu3.context.IWorkerContext; 040import org.hl7.fhir.dstu3.model.*; 041import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 042import org.hl7.fhir.dstu3.model.ExpressionNode.*; 043import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; 044import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; 045import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType; 046import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; 047import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; 048import org.hl7.fhir.exceptions.DefinitionException; 049import org.hl7.fhir.exceptions.FHIRException; 050import org.hl7.fhir.exceptions.PathEngineException; 051import org.hl7.fhir.utilities.Utilities; 052 053import java.math.BigDecimal; 054import java.util.*; 055 056/** 057 * 058 * @author Grahame Grieve 059 * 060 */ 061public class FHIRPathEngine { 062 private IWorkerContext worker; 063 private IEvaluationContext hostServices; 064 private StringBuilder log = new StringBuilder(); 065 private Set<String> primitiveTypes = new HashSet<String>(); 066 private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>(); 067 068 // if the fhir path expressions are allowed to use constants beyond those defined in the specification 069 // the application can implement them by providing a constant resolver 070 public interface IEvaluationContext { 071 public class FunctionDetails { 072 private String description; 073 private int minParameters; 074 private int maxParameters; 075 public FunctionDetails(String description, int minParameters, int maxParameters) { 076 super(); 077 this.description = description; 078 this.minParameters = minParameters; 079 this.maxParameters = maxParameters; 080 } 081 public String getDescription() { 082 return description; 083 } 084 public int getMinParameters() { 085 return minParameters; 086 } 087 public int getMaxParameters() { 088 return maxParameters; 089 } 090 091 } 092 093 /** 094 * A constant reference - e.g. a reference to a name that must be resolved in context. 095 * The % will be removed from the constant name before this is invoked. 096 * 097 * This will also be called if the host invokes the FluentPath engine with a context of null 098 * 099 * @param appContext - content passed into the fluent path engine 100 * @param name - name reference to resolve 101 * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired) 102 */ 103 public Base resolveConstant(Object appContext, String name) throws PathEngineException; 104 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException; 105 106 /** 107 * when the .log() function is called 108 * 109 * @param argument 110 * @param focus 111 * @return 112 */ 113 public boolean log(String argument, List<Base> focus); 114 115 // extensibility for functions 116 /** 117 * 118 * @param functionName 119 * @return null if the function is not known 120 */ 121 public FunctionDetails resolveFunction(String functionName); 122 123 /** 124 * Check the function parameters, and throw an error if they are incorrect, or return the type for the function 125 * @param functionName 126 * @param parameters 127 * @return 128 */ 129 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException; 130 131 /** 132 * @param appContext 133 * @param functionName 134 * @param parameters 135 * @return 136 */ 137 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters); 138 139 /** 140 * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null 141 * @param url 142 * @return 143 */ 144 public Base resolveReference(Object appContext, String url); 145 } 146 147 148 /** 149 * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) 150 */ 151 public FHIRPathEngine(IWorkerContext worker) { 152 super(); 153 this.worker = worker; 154 for (StructureDefinition sd : worker.allStructures()) { 155 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 156 allTypes.put(sd.getName(), sd); 157 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 158 primitiveTypes.add(sd.getName()); 159 } 160 } 161 } 162 163 164 // --- 3 methods to override in children ------------------------------------------------------- 165 // if you don't override, it falls through to the using the base reference implementation 166 // HAPI overrides to these to support extending the base model 167 168 public IEvaluationContext getHostServices() { 169 return hostServices; 170 } 171 172 173 public void setHostServices(IEvaluationContext constantResolver) { 174 this.hostServices = constantResolver; 175 } 176 177 178 /** 179 * Given an item, return all the children that conform to the pattern described in name 180 * 181 * Possible patterns: 182 * - a simple name (which may be the base of a name with [] e.g. value[x]) 183 * - a name with a type replacement e.g. valueCodeableConcept 184 * - * which means all children 185 * - ** which means all descendants 186 * 187 * @param item 188 * @param name 189 * @param result 190 * @throws FHIRException 191 */ 192 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 193 Base[] list = item.listChildrenByName(name, false); 194 if (list != null) 195 for (Base v : list) 196 if (v != null) 197 result.add(v); 198 } 199 200 // --- public API ------------------------------------------------------- 201 /** 202 * Parse a path for later use using execute 203 * 204 * @param path 205 * @return 206 * @throws PathEngineException 207 * @throws Exception 208 */ 209 public ExpressionNode parse(String path) throws FHIRLexerException { 210 FHIRLexer lexer = new FHIRLexer(path); 211 if (lexer.done()) 212 throw lexer.error("Path cannot be empty"); 213 ExpressionNode result = parseExpression(lexer, true); 214 if (!lexer.done()) 215 throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\""); 216 result.check(); 217 return result; 218 } 219 220 /** 221 * Parse a path that is part of some other syntax 222 * 223 * @return 224 * @throws PathEngineException 225 * @throws Exception 226 */ 227 public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException { 228 ExpressionNode result = parseExpression(lexer, true); 229 result.check(); 230 return result; 231 } 232 233 /** 234 * check that paths referred to in the ExpressionNode are valid 235 * 236 * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath 237 * 238 * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context 239 * 240 * @param context - the logical type against which this path is applied 241 * @throws DefinitionException 242 * @throws PathEngineException 243 * @if the path is not valid 244 */ 245 public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 246 // if context is a path that refers to a type, do that conversion now 247 TypeDetails types; 248 if (context == null) { 249 types = null; // this is a special case; the first path reference will have to resolve to something in the context 250 } else if (!context.contains(".")) { 251 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context); 252 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 253 } else { 254 String ctxt = context.substring(0, context.indexOf('.')); 255 if (Utilities.isAbsoluteUrl(resourceType)) { 256 ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt; 257 } 258 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt); 259 if (sd == null) 260 throw new PathEngineException("Unknown context "+context); 261 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 262 if (ed == null) 263 throw new PathEngineException("Unknown context element "+context); 264 if (ed.fixedType != null) 265 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 266 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 267 types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context); 268 else { 269 types = new TypeDetails(CollectionStatus.SINGLETON); 270 for (TypeRefComponent t : ed.getDefinition().getType()) 271 types.addType(t.getCode()); 272 } 273 } 274 275 return executeType(new ExecutionTypeContext(appContext, resourceType, context, types), types, expr, true); 276 } 277 278 public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 279 // if context is a path that refers to a type, do that conversion now 280 TypeDetails types; 281 if (!context.contains(".")) { 282 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); 283 } else { 284 ElementDefinitionMatch ed = getElementDefinition(sd, context, true); 285 if (ed == null) 286 throw new PathEngineException("Unknown context element "+context); 287 if (ed.fixedType != null) 288 types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); 289 else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 290 types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context); 291 else { 292 types = new TypeDetails(CollectionStatus.SINGLETON); 293 for (TypeRefComponent t : ed.getDefinition().getType()) 294 types.addType(t.getCode()); 295 } 296 } 297 298 return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), context, types), types, expr, true); 299 } 300 301 public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { 302 // if context is a path that refers to a type, do that conversion now 303 TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context 304 return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true); 305 } 306 307 public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException { 308 return check(appContext, resourceType, context, parse(expr)); 309 } 310 311 312 /** 313 * evaluate a path and return the matching elements 314 * 315 * @param base - the object against which the path is being evaluated 316 * @param ExpressionNode - the parsed ExpressionNode statement to use 317 * @return 318 * @throws FHIRException 319 * @ 320 */ 321 public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException { 322 List<Base> list = new ArrayList<Base>(); 323 if (base != null) 324 list.add(base); 325 log = new StringBuilder(); 326 return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true); 327 } 328 329 /** 330 * evaluate a path and return the matching elements 331 * 332 * @param base - the object against which the path is being evaluated 333 * @param path - the FHIR Path statement to use 334 * @return 335 * @throws FHIRException 336 * @ 337 */ 338 public List<Base> evaluate(Base base, String path) throws FHIRException { 339 ExpressionNode exp = parse(path); 340 List<Base> list = new ArrayList<Base>(); 341 if (base != null) 342 list.add(base); 343 log = new StringBuilder(); 344 return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true); 345 } 346 347 /** 348 * evaluate a path and return the matching elements 349 * 350 * @param base - the object against which the path is being evaluated 351 * @param ExpressionNode - the parsed ExpressionNode statement to use 352 * @return 353 * @throws FHIRException 354 * @ 355 */ 356 public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 357 List<Base> list = new ArrayList<Base>(); 358 if (base != null) 359 list.add(base); 360 log = new StringBuilder(); 361 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 362 } 363 364 /** 365 * evaluate a path and return the matching elements 366 * 367 * @param base - the object against which the path is being evaluated 368 * @param ExpressionNode - the parsed ExpressionNode statement to use 369 * @return 370 * @throws FHIRException 371 * @ 372 */ 373 public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException { 374 List<Base> list = new ArrayList<Base>(); 375 if (base != null) 376 list.add(base); 377 log = new StringBuilder(); 378 return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true); 379 } 380 381 /** 382 * evaluate a path and return the matching elements 383 * 384 * @param base - the object against which the path is being evaluated 385 * @param path - the FHIR Path statement to use 386 * @return 387 * @throws FHIRException 388 * @ 389 */ 390 public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException { 391 ExpressionNode exp = parse(path); 392 List<Base> list = new ArrayList<Base>(); 393 if (base != null) 394 list.add(base); 395 log = new StringBuilder(); 396 return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true); 397 } 398 399 /** 400 * evaluate a path and return true or false (e.g. for an invariant) 401 * 402 * @param base - the object against which the path is being evaluated 403 * @param path - the FHIR Path statement to use 404 * @return 405 * @throws FHIRException 406 * @ 407 */ 408 public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException { 409 return convertToBoolean(evaluate(null, resource, base, path)); 410 } 411 412 /** 413 * evaluate a path and return true or false (e.g. for an invariant) 414 * 415 * @param base - the object against which the path is being evaluated 416 * @return 417 * @throws FHIRException 418 * @ 419 */ 420 public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException { 421 return convertToBoolean(evaluate(null, resource, base, node)); 422 } 423 424 /** 425 * evaluate a path and return true or false (e.g. for an invariant) 426 * 427 * @param base - the object against which the path is being evaluated 428 * @return 429 * @throws FHIRException 430 * @ 431 */ 432 public boolean evaluateToBoolean(Object appInfo, Resource resource, Base base, ExpressionNode node) throws FHIRException { 433 return convertToBoolean(evaluate(appInfo, resource, base, node)); 434 } 435 436 /** 437 * evaluate a path and return true or false (e.g. for an invariant) 438 * 439 * @param base - the object against which the path is being evaluated 440 * @return 441 * @throws FHIRException 442 * @ 443 */ 444 public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException { 445 return convertToBoolean(evaluate(null, resource, base, node)); 446 } 447 448 /** 449 * evaluate a path and a string containing the outcome (for display) 450 * 451 * @param base - the object against which the path is being evaluated 452 * @param path - the FHIR Path statement to use 453 * @return 454 * @throws FHIRException 455 * @ 456 */ 457 public String evaluateToString(Base base, String path) throws FHIRException { 458 return convertToString(evaluate(base, path)); 459 } 460 461 public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException { 462 return convertToString(evaluate(appInfo, resource, base, node)); 463 } 464 465 /** 466 * worker routine for converting a set of objects to a string representation 467 * 468 * @param items - result from @evaluate 469 * @return 470 */ 471 public String convertToString(List<Base> items) { 472 StringBuilder b = new StringBuilder(); 473 boolean first = true; 474 for (Base item : items) { 475 if (first) 476 first = false; 477 else 478 b.append(','); 479 480 b.append(convertToString(item)); 481 } 482 return b.toString(); 483 } 484 485 private String convertToString(Base item) { 486 if (item.isPrimitive()) 487 return item.primitiveValue(); 488 else 489 return item.toString(); 490 } 491 492 /** 493 * worker routine for converting a set of objects to a boolean representation (for invariants) 494 * 495 * @param items - result from @evaluate 496 * @return 497 */ 498 public boolean convertToBoolean(List<Base> items) { 499 if (items == null) 500 return false; 501 else if (items.size() == 1 && items.get(0) instanceof BooleanType) 502 return ((BooleanType) items.get(0)).getValue(); 503 else 504 return items.size() > 0; 505 } 506 507 508 private void log(String name, List<Base> contents) { 509 if (hostServices == null || !hostServices.log(name, contents)) { 510 if (log.length() > 0) 511 log.append("; "); 512 log.append(name); 513 log.append(": "); 514 boolean first = true; 515 for (Base b : contents) { 516 if (first) 517 first = false; 518 else 519 log.append(","); 520 log.append(convertToString(b)); 521 } 522 } 523 } 524 525 public String forLog() { 526 if (log.length() > 0) 527 return " ("+log.toString()+")"; 528 else 529 return ""; 530 } 531 532 private class ExecutionContext { 533 private Object appInfo; 534 private Base resource; 535 private Base context; 536 private Base thisItem; 537 private Map<String, Base> aliases; 538 539 public ExecutionContext(Object appInfo, Base resource, Base context, Map<String, Base> aliases, Base thisItem) { 540 this.appInfo = appInfo; 541 this.context = context; 542 this.resource = resource; 543 this.aliases = aliases; 544 this.thisItem = thisItem; 545 } 546 public Base getResource() { 547 return resource; 548 } 549 public Base getThisItem() { 550 return thisItem; 551 } 552 public void addAlias(String name, List<Base> focus) throws FHIRException { 553 if (aliases == null) 554 aliases = new HashMap<String, Base>(); 555 else 556 aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 557 if (focus.size() > 1) 558 throw new FHIRException("Attempt to alias a collection, not a singleton"); 559 aliases.put(name, focus.size() == 0 ? null : focus.get(0)); 560 } 561 public Base getAlias(String name) { 562 return aliases == null ? null : aliases.get(name); 563 } 564 } 565 566 private class ExecutionTypeContext { 567 private Object appInfo; 568 private String resource; 569 private String context; 570 private TypeDetails thisItem; 571 572 573 public ExecutionTypeContext(Object appInfo, String resource, String context, TypeDetails thisItem) { 574 super(); 575 this.appInfo = appInfo; 576 this.resource = resource; 577 this.context = context; 578 this.thisItem = thisItem; 579 580 } 581 public String getResource() { 582 return resource; 583 } 584 public TypeDetails getThisItem() { 585 return thisItem; 586 } 587 } 588 589 private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException { 590 ExpressionNode result = new ExpressionNode(lexer.nextId()); 591 SourceLocation c = lexer.getCurrentStartLocation(); 592 result.setStart(lexer.getCurrentLocation()); 593 // special: 594 if (lexer.getCurrent().equals("-")) { 595 lexer.take(); 596 lexer.setCurrent("-"+lexer.getCurrent()); 597 } 598 if (lexer.getCurrent().equals("+")) { 599 lexer.take(); 600 lexer.setCurrent("+"+lexer.getCurrent()); 601 } 602 if (lexer.isConstant(false)) { 603 checkConstant(lexer.getCurrent(), lexer); 604 result.setConstant(lexer.take()); 605 result.setKind(Kind.Constant); 606 result.setEnd(lexer.getCurrentLocation()); 607 } else if ("(".equals(lexer.getCurrent())) { 608 lexer.next(); 609 result.setKind(Kind.Group); 610 result.setGroup(parseExpression(lexer, true)); 611 if (!")".equals(lexer.getCurrent())) 612 throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\""); 613 result.setEnd(lexer.getCurrentLocation()); 614 lexer.next(); 615 } else { 616 if (!lexer.isToken() && !lexer.getCurrent().startsWith("\"")) 617 throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name"); 618 if (lexer.getCurrent().startsWith("\"")) 619 result.setName(lexer.readConstant("Path Name")); 620 else 621 result.setName(lexer.take()); 622 result.setEnd(lexer.getCurrentLocation()); 623 if (!result.checkName()) 624 throw lexer.error("Found "+result.getName()+" expecting a valid token name"); 625 if ("(".equals(lexer.getCurrent())) { 626 Function f = Function.fromCode(result.getName()); 627 FunctionDetails details = null; 628 if (f == null) { 629 if (hostServices != null) 630 details = hostServices.resolveFunction(result.getName()); 631 if (details == null) 632 throw lexer.error("The name "+result.getName()+" is not a valid function name"); 633 f = Function.Custom; 634 } 635 result.setKind(Kind.Function); 636 result.setFunction(f); 637 lexer.next(); 638 while (!")".equals(lexer.getCurrent())) { 639 result.getParameters().add(parseExpression(lexer, true)); 640 if (",".equals(lexer.getCurrent())) 641 lexer.next(); 642 else if (!")".equals(lexer.getCurrent())) 643 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected"); 644 } 645 result.setEnd(lexer.getCurrentLocation()); 646 lexer.next(); 647 checkParameters(lexer, c, result, details); 648 } else 649 result.setKind(Kind.Name); 650 } 651 ExpressionNode focus = result; 652 if ("[".equals(lexer.getCurrent())) { 653 lexer.next(); 654 ExpressionNode item = new ExpressionNode(lexer.nextId()); 655 item.setKind(Kind.Function); 656 item.setFunction(ExpressionNode.Function.Item); 657 item.getParameters().add(parseExpression(lexer, true)); 658 if (!lexer.getCurrent().equals("]")) 659 throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected"); 660 lexer.next(); 661 result.setInner(item); 662 focus = item; 663 } 664 if (".".equals(lexer.getCurrent())) { 665 lexer.next(); 666 focus.setInner(parseExpression(lexer, false)); 667 } 668 result.setProximal(proximal); 669 if (proximal) { 670 while (lexer.isOp()) { 671 focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent())); 672 focus.setOpStart(lexer.getCurrentStartLocation()); 673 focus.setOpEnd(lexer.getCurrentLocation()); 674 lexer.next(); 675 focus.setOpNext(parseExpression(lexer, false)); 676 focus = focus.getOpNext(); 677 } 678 result = organisePrecedence(lexer, result); 679 } 680 return result; 681 } 682 683 private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) { 684 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 685 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 686 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 687 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThen, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual)); 688 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is)); 689 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent)); 690 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And)); 691 node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or)); 692 // last: implies 693 return node; 694 } 695 696 private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) { 697 // work : boolean; 698 // focus, node, group : ExpressionNode; 699 700 assert(start.isProximal()); 701 702 // is there anything to do? 703 boolean work = false; 704 ExpressionNode focus = start.getOpNext(); 705 if (ops.contains(start.getOperation())) { 706 while (focus != null && focus.getOperation() != null) { 707 work = work || !ops.contains(focus.getOperation()); 708 focus = focus.getOpNext(); 709 } 710 } else { 711 while (focus != null && focus.getOperation() != null) { 712 work = work || ops.contains(focus.getOperation()); 713 focus = focus.getOpNext(); 714 } 715 } 716 if (!work) 717 return start; 718 719 // entry point: tricky 720 ExpressionNode group; 721 if (ops.contains(start.getOperation())) { 722 group = newGroup(lexer, start); 723 group.setProximal(true); 724 focus = start; 725 start = group; 726 } else { 727 ExpressionNode node = start; 728 729 focus = node.getOpNext(); 730 while (!ops.contains(focus.getOperation())) { 731 node = focus; 732 focus = focus.getOpNext(); 733 } 734 group = newGroup(lexer, focus); 735 node.setOpNext(group); 736 } 737 738 // now, at this point: 739 // group is the group we are adding to, it already has a .group property filled out. 740 // focus points at the group.group 741 do { 742 // run until we find the end of the sequence 743 while (ops.contains(focus.getOperation())) 744 focus = focus.getOpNext(); 745 if (focus.getOperation() != null) { 746 group.setOperation(focus.getOperation()); 747 group.setOpNext(focus.getOpNext()); 748 focus.setOperation(null); 749 focus.setOpNext(null); 750 // now look for another sequence, and start it 751 ExpressionNode node = group; 752 focus = group.getOpNext(); 753 if (focus != null) { 754 while (focus != null && !ops.contains(focus.getOperation())) { 755 node = focus; 756 focus = focus.getOpNext(); 757 } 758 if (focus != null) { // && (focus.Operation in Ops) - must be true 759 group = newGroup(lexer, focus); 760 node.setOpNext(group); 761 } 762 } 763 } 764 } 765 while (focus != null && focus.getOperation() != null); 766 return start; 767 } 768 769 770 private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) { 771 ExpressionNode result = new ExpressionNode(lexer.nextId()); 772 result.setKind(Kind.Group); 773 result.setGroup(next); 774 result.getGroup().setProximal(true); 775 return result; 776 } 777 778 private void checkConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 779 if (s.startsWith("\'") && s.endsWith("\'")) { 780 int i = 1; 781 while (i < s.length()-1) { 782 char ch = s.charAt(i); 783 if (ch == '\\') { 784 switch (ch) { 785 case 't': 786 case 'r': 787 case 'n': 788 case 'f': 789 case '\'': 790 case '\\': 791 case '/': 792 i++; 793 break; 794 case 'u': 795 if (!Utilities.isHex("0x"+s.substring(i, i+4))) 796 throw lexer.error("Improper unicode escape \\u"+s.substring(i, i+4)); 797 break; 798 default: 799 throw lexer.error("Unknown character escape \\"+ch); 800 } 801 } else 802 i++; 803 } 804 } 805 } 806 807 // procedure CheckParamCount(c : integer); 808 // begin 809 // if exp.Parameters.Count <> c then 810 // raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset); 811 // end; 812 813 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException { 814 if (exp.getParameters().size() != count) 815 throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString()); 816 return true; 817 } 818 819 private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException { 820 if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) 821 throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString()); 822 return true; 823 } 824 825 private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException { 826 switch (exp.getFunction()) { 827 case Empty: return checkParamCount(lexer, location, exp, 0); 828 case Not: return checkParamCount(lexer, location, exp, 0); 829 case Exists: return checkParamCount(lexer, location, exp, 0); 830 case SubsetOf: return checkParamCount(lexer, location, exp, 1); 831 case SupersetOf: return checkParamCount(lexer, location, exp, 1); 832 case IsDistinct: return checkParamCount(lexer, location, exp, 0); 833 case Distinct: return checkParamCount(lexer, location, exp, 0); 834 case Count: return checkParamCount(lexer, location, exp, 0); 835 case Where: return checkParamCount(lexer, location, exp, 1); 836 case Select: return checkParamCount(lexer, location, exp, 1); 837 case All: return checkParamCount(lexer, location, exp, 0, 1); 838 case Repeat: return checkParamCount(lexer, location, exp, 1); 839 case Item: return checkParamCount(lexer, location, exp, 1); 840 case As: return checkParamCount(lexer, location, exp, 1); 841 case Is: return checkParamCount(lexer, location, exp, 1); 842 case Single: return checkParamCount(lexer, location, exp, 0); 843 case First: return checkParamCount(lexer, location, exp, 0); 844 case Last: return checkParamCount(lexer, location, exp, 0); 845 case Tail: return checkParamCount(lexer, location, exp, 0); 846 case Skip: return checkParamCount(lexer, location, exp, 1); 847 case Take: return checkParamCount(lexer, location, exp, 1); 848 case Iif: return checkParamCount(lexer, location, exp, 2,3); 849 case ToInteger: return checkParamCount(lexer, location, exp, 0); 850 case ToDecimal: return checkParamCount(lexer, location, exp, 0); 851 case ToString: return checkParamCount(lexer, location, exp, 0); 852 case Substring: return checkParamCount(lexer, location, exp, 1, 2); 853 case StartsWith: return checkParamCount(lexer, location, exp, 1); 854 case EndsWith: return checkParamCount(lexer, location, exp, 1); 855 case Matches: return checkParamCount(lexer, location, exp, 1); 856 case ReplaceMatches: return checkParamCount(lexer, location, exp, 2); 857 case Contains: return checkParamCount(lexer, location, exp, 1); 858 case Replace: return checkParamCount(lexer, location, exp, 2); 859 case Length: return checkParamCount(lexer, location, exp, 0); 860 case Children: return checkParamCount(lexer, location, exp, 0); 861 case Descendants: return checkParamCount(lexer, location, exp, 0); 862 case MemberOf: return checkParamCount(lexer, location, exp, 1); 863 case Trace: return checkParamCount(lexer, location, exp, 1); 864 case Today: return checkParamCount(lexer, location, exp, 0); 865 case Now: return checkParamCount(lexer, location, exp, 0); 866 case Resolve: return checkParamCount(lexer, location, exp, 0); 867 case Extension: return checkParamCount(lexer, location, exp, 1); 868 case HasValue: return checkParamCount(lexer, location, exp, 0); 869 case Alias: return checkParamCount(lexer, location, exp, 1); 870 case AliasAs: return checkParamCount(lexer, location, exp, 1); 871 case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); 872 } 873 return false; 874 } 875 876 private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException { 877// System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString()); 878 List<Base> work = new ArrayList<Base>(); 879 switch (exp.getKind()) { 880 case Name: 881 if (atEntry && exp.getName().equals("$this")) 882 work.add(context.getThisItem()); 883 else 884 for (Base item : focus) { 885 List<Base> outcome = execute(context, item, exp, atEntry); 886 for (Base base : outcome) 887 if (base != null) 888 work.add(base); 889 } 890 break; 891 case Function: 892 List<Base> work2 = evaluateFunction(context, focus, exp); 893 work.addAll(work2); 894 break; 895 case Constant: 896 Base b = processConstant(context, exp.getConstant()); 897 if (b != null) 898 work.add(b); 899 break; 900 case Group: 901 work2 = execute(context, focus, exp.getGroup(), atEntry); 902 work.addAll(work2); 903 } 904 905 if (exp.getInner() != null) 906 work = execute(context, work, exp.getInner(), false); 907 908 if (exp.isProximal() && exp.getOperation() != null) { 909 ExpressionNode next = exp.getOpNext(); 910 ExpressionNode last = exp; 911 while (next != null) { 912 List<Base> work2 = preOperate(work, last.getOperation()); 913 if (work2 != null) 914 work = work2; 915 else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) { 916 work2 = executeTypeName(context, focus, next, false); 917 work = operate(work, last.getOperation(), work2); 918 } else { 919 work2 = execute(context, focus, next, true); 920 work = operate(work, last.getOperation(), work2); 921// System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString()); 922 } 923 last = next; 924 next = next.getOpNext(); 925 } 926 } 927// System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString()); 928 return work; 929 } 930 931 private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) { 932 List<Base> result = new ArrayList<Base>(); 933 result.add(new StringType(next.getName())); 934 return result; 935 } 936 937 938 private List<Base> preOperate(List<Base> left, Operation operation) { 939 switch (operation) { 940 case And: 941 return isBoolean(left, false) ? makeBoolean(false) : null; 942 case Or: 943 return isBoolean(left, true) ? makeBoolean(true) : null; 944 case Implies: 945 return convertToBoolean(left) ? null : makeBoolean(true); 946 default: 947 return null; 948 } 949 } 950 951 private List<Base> makeBoolean(boolean b) { 952 List<Base> res = new ArrayList<Base>(); 953 res.add(new BooleanType(b)); 954 return res; 955 } 956 957 private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 958 return new TypeDetails(CollectionStatus.SINGLETON, exp.getName()); 959 } 960 961 private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 962 TypeDetails result = new TypeDetails(null); 963 switch (exp.getKind()) { 964 case Name: 965 if (atEntry && exp.getName().equals("$this")) 966 result.update(context.getThisItem()); 967 else if (atEntry && focus == null) 968 result.update(executeContextType(context, exp.getName())); 969 else { 970 for (String s : focus.getTypes()) { 971 result.update(executeType(s, exp, atEntry)); 972 } 973 if (result.hasNoTypes()) 974 throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe()); 975 } 976 break; 977 case Function: 978 result.update(evaluateFunctionType(context, focus, exp)); 979 break; 980 case Constant: 981 result.update(readConstantType(context, exp.getConstant())); 982 break; 983 case Group: 984 result.update(executeType(context, focus, exp.getGroup(), atEntry)); 985 } 986 exp.setTypes(result); 987 988 if (exp.getInner() != null) { 989 result = executeType(context, result, exp.getInner(), false); 990 } 991 992 if (exp.isProximal() && exp.getOperation() != null) { 993 ExpressionNode next = exp.getOpNext(); 994 ExpressionNode last = exp; 995 while (next != null) { 996 TypeDetails work; 997 if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) 998 work = executeTypeName(context, focus, next, atEntry); 999 else 1000 work = executeType(context, focus, next, atEntry); 1001 result = operateTypes(result, last.getOperation(), work); 1002 last = next; 1003 next = next.getOpNext(); 1004 } 1005 exp.setOpTypes(result); 1006 } 1007 return result; 1008 } 1009 1010 private Base processConstant(ExecutionContext context, String constant) throws PathEngineException { 1011 if (constant.equals("true")) { 1012 return new BooleanType(true); 1013 } else if (constant.equals("false")) { 1014 return new BooleanType(false); 1015 } else if (constant.equals("{}")) { 1016 return null; 1017 } else if (Utilities.isInteger(constant)) { 1018 return new IntegerType(constant); 1019 } else if (Utilities.isDecimal(constant, false)) { 1020 return new DecimalType(constant); 1021 } else if (constant.startsWith("\'")) { 1022 return new StringType(processConstantString(constant)); 1023 } else if (constant.startsWith("%")) { 1024 return resolveConstant(context, constant); 1025 } else if (constant.startsWith("@")) { 1026 return processDateConstant(context.appInfo, constant.substring(1)); 1027 } else { 1028 return new StringType(constant); 1029 } 1030 } 1031 1032 private Base processDateConstant(Object appInfo, String value) throws PathEngineException { 1033 if (value.startsWith("T")) 1034 return new TimeType(value.substring(1)); 1035 String v = value; 1036 if (v.length() > 10) { 1037 int i = v.substring(10).indexOf("-"); 1038 if (i == -1) 1039 i = v.substring(10).indexOf("+"); 1040 if (i == -1) 1041 i = v.substring(10).indexOf("Z"); 1042 v = i == -1 ? value : v.substring(0, 10+i); 1043 } 1044 if (v.length() > 10) 1045 return new DateTimeType(value); 1046 else 1047 return new DateType(value); 1048 } 1049 1050 1051 private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException { 1052 if (s.equals("%sct")) 1053 return new StringType("http://snomed.info/sct"); 1054 else if (s.equals("%loinc")) 1055 return new StringType("http://loinc.org"); 1056 else if (s.equals("%ucum")) 1057 return new StringType("http://unitsofmeasure.org"); 1058 else if (s.equals("%resource")) { 1059 if (context.resource == null) 1060 throw new PathEngineException("Cannot use %resource in this context"); 1061 return context.resource; 1062 } else if (s.equals("%context")) { 1063 return context.context; 1064 } else if (s.equals("%us-zip")) 1065 return new StringType("[0-9]{5}(-[0-9]{4}){0,1}"); 1066 else if (s.startsWith("%\"vs-")) 1067 return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+""); 1068 else if (s.startsWith("%\"cs-")) 1069 return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+""); 1070 else if (s.startsWith("%\"ext-")) 1071 return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)); 1072 else if (hostServices == null) 1073 throw new PathEngineException("Unknown fixed constant '"+s+"'"); 1074 else 1075 return hostServices.resolveConstant(context.appInfo, s.substring(1)); 1076 } 1077 1078 1079 private String processConstantString(String s) throws PathEngineException { 1080 StringBuilder b = new StringBuilder(); 1081 int i = 1; 1082 while (i < s.length()-1) { 1083 char ch = s.charAt(i); 1084 if (ch == '\\') { 1085 i++; 1086 switch (s.charAt(i)) { 1087 case 't': 1088 b.append('\t'); 1089 break; 1090 case 'r': 1091 b.append('\r'); 1092 break; 1093 case 'n': 1094 b.append('\n'); 1095 break; 1096 case 'f': 1097 b.append('\f'); 1098 break; 1099 case '\'': 1100 b.append('\''); 1101 break; 1102 case '\\': 1103 b.append('\\'); 1104 break; 1105 case '/': 1106 b.append('/'); 1107 break; 1108 case 'u': 1109 i++; 1110 int uc = Integer.parseInt(s.substring(i, i+4), 16); 1111 b.append((char) uc); 1112 i = i + 3; 1113 break; 1114 default: 1115 throw new PathEngineException("Unknown character escape \\"+s.charAt(i)); 1116 } 1117 i++; 1118 } else { 1119 b.append(ch); 1120 i++; 1121 } 1122 } 1123 return b.toString(); 1124 } 1125 1126 1127 private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException { 1128 switch (operation) { 1129 case Equals: return opEquals(left, right); 1130 case Equivalent: return opEquivalent(left, right); 1131 case NotEquals: return opNotEquals(left, right); 1132 case NotEquivalent: return opNotEquivalent(left, right); 1133 case LessThen: return opLessThen(left, right); 1134 case Greater: return opGreater(left, right); 1135 case LessOrEqual: return opLessOrEqual(left, right); 1136 case GreaterOrEqual: return opGreaterOrEqual(left, right); 1137 case Union: return opUnion(left, right); 1138 case In: return opIn(left, right); 1139 case Contains: return opContains(left, right); 1140 case Or: return opOr(left, right); 1141 case And: return opAnd(left, right); 1142 case Xor: return opXor(left, right); 1143 case Implies: return opImplies(left, right); 1144 case Plus: return opPlus(left, right); 1145 case Times: return opTimes(left, right); 1146 case Minus: return opMinus(left, right); 1147 case Concatenate: return opConcatenate(left, right); 1148 case DivideBy: return opDivideBy(left, right); 1149 case Div: return opDiv(left, right); 1150 case Mod: return opMod(left, right); 1151 case Is: return opIs(left, right); 1152 case As: return opAs(left, right); 1153 default: 1154 throw new Error("Not Done Yet: "+operation.toCode()); 1155 } 1156 } 1157 1158 private List<Base> opAs(List<Base> left, List<Base> right) { 1159 List<Base> result = new ArrayList<Base>(); 1160 if (left.size() != 1 || right.size() != 1) 1161 return result; 1162 else { 1163 String tn = convertToString(right); 1164 if (tn.equals(left.get(0).fhirType())) 1165 result.add(left.get(0)); 1166 } 1167 return result; 1168 } 1169 1170 1171 private List<Base> opIs(List<Base> left, List<Base> right) { 1172 List<Base> result = new ArrayList<Base>(); 1173 if (left.size() != 1 || right.size() != 1) 1174 result.add(new BooleanType(false)); 1175 else { 1176 String tn = convertToString(right); 1177 result.add(new BooleanType(left.get(0).hasType(tn))); 1178 } 1179 return result; 1180 } 1181 1182 1183 private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) { 1184 switch (operation) { 1185 case Equals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1186 case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1187 case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1188 case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1189 case LessThen: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1190 case Greater: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1191 case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1192 case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1193 case Is: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1194 case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes()); 1195 case Union: return left.union(right); 1196 case Or: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1197 case And: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1198 case Xor: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1199 case Implies : return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1200 case Times: 1201 TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON); 1202 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1203 result.addType("integer"); 1204 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1205 result.addType("decimal"); 1206 return result; 1207 case DivideBy: 1208 result = new TypeDetails(CollectionStatus.SINGLETON); 1209 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1210 result.addType("decimal"); 1211 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1212 result.addType("decimal"); 1213 return result; 1214 case Concatenate: 1215 result = new TypeDetails(CollectionStatus.SINGLETON, ""); 1216 return result; 1217 case Plus: 1218 result = new TypeDetails(CollectionStatus.SINGLETON); 1219 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1220 result.addType("integer"); 1221 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1222 result.addType("decimal"); 1223 else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) 1224 result.addType("string"); 1225 return result; 1226 case Minus: 1227 result = new TypeDetails(CollectionStatus.SINGLETON); 1228 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1229 result.addType("integer"); 1230 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1231 result.addType("decimal"); 1232 return result; 1233 case Div: 1234 case Mod: 1235 result = new TypeDetails(CollectionStatus.SINGLETON); 1236 if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) 1237 result.addType("integer"); 1238 else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) 1239 result.addType("decimal"); 1240 return result; 1241 case In: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1242 case Contains: return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1243 default: 1244 return null; 1245 } 1246 } 1247 1248 1249 private List<Base> opEquals(List<Base> left, List<Base> right) { 1250 if (left.size() != right.size()) 1251 return makeBoolean(false); 1252 1253 boolean res = true; 1254 for (int i = 0; i < left.size(); i++) { 1255 if (!doEquals(left.get(i), right.get(i))) { 1256 res = false; 1257 break; 1258 } 1259 } 1260 return makeBoolean(res); 1261 } 1262 1263 private List<Base> opNotEquals(List<Base> left, List<Base> right) { 1264 if (left.size() != right.size()) 1265 return makeBoolean(true); 1266 1267 boolean res = true; 1268 for (int i = 0; i < left.size(); i++) { 1269 if (!doEquals(left.get(i), right.get(i))) { 1270 res = false; 1271 break; 1272 } 1273 } 1274 return makeBoolean(!res); 1275 } 1276 1277 private boolean doEquals(Base left, Base right) { 1278 if (left.isPrimitive() && right.isPrimitive()) 1279 return Base.equals(left.primitiveValue(), right.primitiveValue()); 1280 else 1281 return Base.compareDeep(left, right, false); 1282 } 1283 1284 private boolean doEquivalent(Base left, Base right) throws PathEngineException { 1285 if (left.hasType("integer") && right.hasType("integer")) 1286 return doEquals(left, right); 1287 if (left.hasType("boolean") && right.hasType("boolean")) 1288 return doEquals(left, right); 1289 if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) 1290 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1291 if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) 1292 return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue()); 1293 if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri")) 1294 return Utilities.equivalent(convertToString(left), convertToString(right)); 1295 1296 throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType())); 1297 } 1298 1299 private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1300 if (left.size() != right.size()) 1301 return makeBoolean(false); 1302 1303 boolean res = true; 1304 for (int i = 0; i < left.size(); i++) { 1305 boolean found = false; 1306 for (int j = 0; j < right.size(); j++) { 1307 if (doEquivalent(left.get(i), right.get(j))) { 1308 found = true; 1309 break; 1310 } 1311 } 1312 if (!found) { 1313 res = false; 1314 break; 1315 } 1316 } 1317 return makeBoolean(res); 1318 } 1319 1320 private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException { 1321 if (left.size() != right.size()) 1322 return makeBoolean(true); 1323 1324 boolean res = true; 1325 for (int i = 0; i < left.size(); i++) { 1326 boolean found = false; 1327 for (int j = 0; j < right.size(); j++) { 1328 if (doEquivalent(left.get(i), right.get(j))) { 1329 found = true; 1330 break; 1331 } 1332 } 1333 if (!found) { 1334 res = false; 1335 break; 1336 } 1337 } 1338 return makeBoolean(!res); 1339 } 1340 1341 private List<Base> opLessThen(List<Base> left, List<Base> right) throws FHIRException { 1342 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1343 Base l = left.get(0); 1344 Base r = right.get(0); 1345 if (l.hasType("string") && r.hasType("string")) 1346 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1347 else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 1348 return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue())); 1349 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1350 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1351 else if ((l.hasType("time")) && (r.hasType("time"))) 1352 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0); 1353 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1354 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1355 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1356 if (Base.compareDeep(lUnit, rUnit, true)) { 1357 return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1358 } else { 1359 throw new InternalErrorException("Canonical Comparison isn't done yet"); 1360 } 1361 } 1362 return new ArrayList<Base>(); 1363 } 1364 1365 private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException { 1366 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1367 Base l = left.get(0); 1368 Base r = right.get(0); 1369 if (l.hasType("string") && r.hasType("string")) 1370 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1371 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1372 return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue())); 1373 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1374 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1375 else if ((l.hasType("time")) && (r.hasType("time"))) 1376 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0); 1377 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1378 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1379 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1380 if (Base.compareDeep(lUnit, rUnit, true)) { 1381 return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1382 } else { 1383 throw new InternalErrorException("Canonical Comparison isn't done yet"); 1384 } 1385 } 1386 return new ArrayList<Base>(); 1387 } 1388 1389 private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1390 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1391 Base l = left.get(0); 1392 Base r = right.get(0); 1393 if (l.hasType("string") && r.hasType("string")) 1394 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1395 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1396 return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue())); 1397 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1398 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1399 else if ((l.hasType("time")) && (r.hasType("time"))) 1400 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0); 1401 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1402 List<Base> lUnits = left.get(0).listChildrenByName("unit"); 1403 String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null; 1404 List<Base> rUnits = right.get(0).listChildrenByName("unit"); 1405 String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null; 1406 if ((lunit == null && runit == null) || lunit.equals(runit)) { 1407 return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1408 } else { 1409 throw new InternalErrorException("Canonical Comparison isn't done yet"); 1410 } 1411 } 1412 return new ArrayList<Base>(); 1413 } 1414 1415 private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException { 1416 if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) { 1417 Base l = left.get(0); 1418 Base r = right.get(0); 1419 if (l.hasType("string") && r.hasType("string")) 1420 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1421 else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 1422 return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue())); 1423 else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) 1424 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1425 else if ((l.hasType("time")) && (r.hasType("time"))) 1426 return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0); 1427 } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) { 1428 List<Base> lUnit = left.get(0).listChildrenByName("unit"); 1429 List<Base> rUnit = right.get(0).listChildrenByName("unit"); 1430 if (Base.compareDeep(lUnit, rUnit, true)) { 1431 return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value")); 1432 } else { 1433 throw new InternalErrorException("Canonical Comparison isn't done yet"); 1434 } 1435 } 1436 return new ArrayList<Base>(); 1437 } 1438 1439 private List<Base> opIn(List<Base> left, List<Base> right) { 1440 boolean ans = true; 1441 for (Base l : left) { 1442 boolean f = false; 1443 for (Base r : right) 1444 if (doEquals(l, r)) { 1445 f = true; 1446 break; 1447 } 1448 if (!f) { 1449 ans = false; 1450 break; 1451 } 1452 } 1453 return makeBoolean(ans); 1454 } 1455 1456 private List<Base> opContains(List<Base> left, List<Base> right) { 1457 boolean ans = true; 1458 for (Base r : right) { 1459 boolean f = false; 1460 for (Base l : left) 1461 if (doEquals(l, r)) { 1462 f = true; 1463 break; 1464 } 1465 if (!f) { 1466 ans = false; 1467 break; 1468 } 1469 } 1470 return makeBoolean(ans); 1471 } 1472 1473 private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException { 1474 if (left.size() == 0) 1475 throw new PathEngineException("Error performing +: left operand has no value"); 1476 if (left.size() > 1) 1477 throw new PathEngineException("Error performing +: left operand has more than one value"); 1478 if (!left.get(0).isPrimitive()) 1479 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1480 if (right.size() == 0) 1481 throw new PathEngineException("Error performing +: right operand has no value"); 1482 if (right.size() > 1) 1483 throw new PathEngineException("Error performing +: right operand has more than one value"); 1484 if (!right.get(0).isPrimitive()) 1485 throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType())); 1486 1487 List<Base> result = new ArrayList<Base>(); 1488 Base l = left.get(0); 1489 Base r = right.get(0); 1490 if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri")) 1491 result.add(new StringType(l.primitiveValue() + r.primitiveValue())); 1492 else if (l.hasType("integer") && r.hasType("integer")) 1493 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue()))); 1494 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1495 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue())))); 1496 else 1497 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())); 1498 return result; 1499 } 1500 1501 private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException { 1502 if (left.size() == 0) 1503 throw new PathEngineException("Error performing *: left operand has no value"); 1504 if (left.size() > 1) 1505 throw new PathEngineException("Error performing *: left operand has more than one value"); 1506 if (!left.get(0).isPrimitive()) 1507 throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType())); 1508 if (right.size() == 0) 1509 throw new PathEngineException("Error performing *: right operand has no value"); 1510 if (right.size() > 1) 1511 throw new PathEngineException("Error performing *: right operand has more than one value"); 1512 if (!right.get(0).isPrimitive()) 1513 throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType())); 1514 1515 List<Base> result = new ArrayList<Base>(); 1516 Base l = left.get(0); 1517 Base r = right.get(0); 1518 1519 if (l.hasType("integer") && r.hasType("integer")) 1520 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue()))); 1521 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1522 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue())))); 1523 else 1524 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())); 1525 return result; 1526 } 1527 1528 private List<Base> opConcatenate(List<Base> left, List<Base> right) { 1529 List<Base> result = new ArrayList<Base>(); 1530 result.add(new StringType(convertToString(left) + convertToString((right)))); 1531 return result; 1532 } 1533 1534 private List<Base> opUnion(List<Base> left, List<Base> right) { 1535 List<Base> result = new ArrayList<Base>(); 1536 for (Base item : left) { 1537 if (!doContains(result, item)) 1538 result.add(item); 1539 } 1540 for (Base item : right) { 1541 if (!doContains(result, item)) 1542 result.add(item); 1543 } 1544 return result; 1545 } 1546 1547 private boolean doContains(List<Base> list, Base item) { 1548 for (Base test : list) 1549 if (doEquals(test, item)) 1550 return true; 1551 return false; 1552 } 1553 1554 1555 private List<Base> opAnd(List<Base> left, List<Base> right) { 1556 if (left.isEmpty() && right.isEmpty()) 1557 return new ArrayList<Base>(); 1558 else if (isBoolean(left, false) || isBoolean(right, false)) 1559 return makeBoolean(false); 1560 else if (left.isEmpty() || right.isEmpty()) 1561 return new ArrayList<Base>(); 1562 else if (convertToBoolean(left) && convertToBoolean(right)) 1563 return makeBoolean(true); 1564 else 1565 return makeBoolean(false); 1566 } 1567 1568 private boolean isBoolean(List<Base> list, boolean b) { 1569 return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b; 1570 } 1571 1572 private List<Base> opOr(List<Base> left, List<Base> right) { 1573 if (left.isEmpty() && right.isEmpty()) 1574 return new ArrayList<Base>(); 1575 else if (convertToBoolean(left) || convertToBoolean(right)) 1576 return makeBoolean(true); 1577 else if (left.isEmpty() || right.isEmpty()) 1578 return new ArrayList<Base>(); 1579 else 1580 return makeBoolean(false); 1581 } 1582 1583 private List<Base> opXor(List<Base> left, List<Base> right) { 1584 if (left.isEmpty() || right.isEmpty()) 1585 return new ArrayList<Base>(); 1586 else 1587 return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right)); 1588 } 1589 1590 private List<Base> opImplies(List<Base> left, List<Base> right) { 1591 if (!convertToBoolean(left)) 1592 return makeBoolean(true); 1593 else if (right.size() == 0) 1594 return new ArrayList<Base>(); 1595 else 1596 return makeBoolean(convertToBoolean(right)); 1597 } 1598 1599 1600 private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException { 1601 if (left.size() == 0) 1602 throw new PathEngineException("Error performing -: left operand has no value"); 1603 if (left.size() > 1) 1604 throw new PathEngineException("Error performing -: left operand has more than one value"); 1605 if (!left.get(0).isPrimitive()) 1606 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 1607 if (right.size() == 0) 1608 throw new PathEngineException("Error performing -: right operand has no value"); 1609 if (right.size() > 1) 1610 throw new PathEngineException("Error performing -: right operand has more than one value"); 1611 if (!right.get(0).isPrimitive()) 1612 throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType())); 1613 1614 List<Base> result = new ArrayList<Base>(); 1615 Base l = left.get(0); 1616 Base r = right.get(0); 1617 1618 if (l.hasType("integer") && r.hasType("integer")) 1619 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue()))); 1620 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 1621 result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue())))); 1622 else 1623 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())); 1624 return result; 1625 } 1626 1627 private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException { 1628 if (left.size() == 0) 1629 throw new PathEngineException("Error performing /: left operand has no value"); 1630 if (left.size() > 1) 1631 throw new PathEngineException("Error performing /: left operand has more than one value"); 1632 if (!left.get(0).isPrimitive()) 1633 throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType())); 1634 if (right.size() == 0) 1635 throw new PathEngineException("Error performing /: right operand has no value"); 1636 if (right.size() > 1) 1637 throw new PathEngineException("Error performing /: right operand has more than one value"); 1638 if (!right.get(0).isPrimitive()) 1639 throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType())); 1640 1641 List<Base> result = new ArrayList<Base>(); 1642 Base l = left.get(0); 1643 Base r = right.get(0); 1644 1645 if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) { 1646 Decimal d1; 1647 try { 1648 d1 = new Decimal(l.primitiveValue()); 1649 Decimal d2 = new Decimal(r.primitiveValue()); 1650 result.add(new DecimalType(d1.divide(d2).asDecimal())); 1651 } catch (UcumException e) { 1652 throw new PathEngineException(e); 1653 } 1654 } 1655 else 1656 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())); 1657 return result; 1658 } 1659 1660 private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException { 1661 if (left.size() == 0) 1662 throw new PathEngineException("Error performing div: left operand has no value"); 1663 if (left.size() > 1) 1664 throw new PathEngineException("Error performing div: left operand has more than one value"); 1665 if (!left.get(0).isPrimitive()) 1666 throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType())); 1667 if (right.size() == 0) 1668 throw new PathEngineException("Error performing div: right operand has no value"); 1669 if (right.size() > 1) 1670 throw new PathEngineException("Error performing div: right operand has more than one value"); 1671 if (!right.get(0).isPrimitive()) 1672 throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType())); 1673 1674 List<Base> result = new ArrayList<Base>(); 1675 Base l = left.get(0); 1676 Base r = right.get(0); 1677 1678 if (l.hasType("integer") && r.hasType("integer")) 1679 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue()))); 1680 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 1681 Decimal d1; 1682 try { 1683 d1 = new Decimal(l.primitiveValue()); 1684 Decimal d2 = new Decimal(r.primitiveValue()); 1685 result.add(new IntegerType(d1.divInt(d2).asDecimal())); 1686 } catch (UcumException e) { 1687 throw new PathEngineException(e); 1688 } 1689 } 1690 else 1691 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())); 1692 return result; 1693 } 1694 1695 private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException { 1696 if (left.size() == 0) 1697 throw new PathEngineException("Error performing mod: left operand has no value"); 1698 if (left.size() > 1) 1699 throw new PathEngineException("Error performing mod: left operand has more than one value"); 1700 if (!left.get(0).isPrimitive()) 1701 throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType())); 1702 if (right.size() == 0) 1703 throw new PathEngineException("Error performing mod: right operand has no value"); 1704 if (right.size() > 1) 1705 throw new PathEngineException("Error performing mod: right operand has more than one value"); 1706 if (!right.get(0).isPrimitive()) 1707 throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType())); 1708 1709 List<Base> result = new ArrayList<Base>(); 1710 Base l = left.get(0); 1711 Base r = right.get(0); 1712 1713 if (l.hasType("integer") && r.hasType("integer")) 1714 result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue()))); 1715 else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 1716 Decimal d1; 1717 try { 1718 d1 = new Decimal(l.primitiveValue()); 1719 Decimal d2 = new Decimal(r.primitiveValue()); 1720 result.add(new DecimalType(d1.modulo(d2).asDecimal())); 1721 } catch (UcumException e) { 1722 throw new PathEngineException(e); 1723 } 1724 } 1725 else 1726 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())); 1727 return result; 1728 } 1729 1730 1731 private TypeDetails readConstantType(ExecutionTypeContext context, String constant) throws PathEngineException { 1732 if (constant.equals("true")) 1733 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1734 else if (constant.equals("false")) 1735 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1736 else if (Utilities.isInteger(constant)) 1737 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1738 else if (Utilities.isDecimal(constant, false)) 1739 return new TypeDetails(CollectionStatus.SINGLETON, "decimal"); 1740 else if (constant.startsWith("%")) 1741 return resolveConstantType(context, constant); 1742 else 1743 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1744 } 1745 1746 private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException { 1747 if (s.equals("%sct")) 1748 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1749 else if (s.equals("%loinc")) 1750 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1751 else if (s.equals("%ucum")) 1752 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1753 else if (s.equals("%resource")) { 1754 if (context.resource == null) 1755 throw new PathEngineException("%resource cannot be used in this context"); 1756 return new TypeDetails(CollectionStatus.SINGLETON, context.resource); 1757 } else if (s.equals("%context")) { 1758 return new TypeDetails(CollectionStatus.SINGLETON, context.context); 1759 } else if (s.equals("%map-codes")) 1760 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1761 else if (s.equals("%us-zip")) 1762 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1763 else if (s.startsWith("%\"vs-")) 1764 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1765 else if (s.startsWith("%\"cs-")) 1766 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1767 else if (s.startsWith("%\"ext-")) 1768 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1769 else if (hostServices == null) 1770 throw new PathEngineException("Unknown fixed constant type for '"+s+"'"); 1771 else 1772 return hostServices.resolveConstantType(context.appInfo, s); 1773 } 1774 1775 private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException { 1776 List<Base> result = new ArrayList<Base>(); 1777 if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up 1778 if (item.isResource() && item.fhirType().equals(exp.getName())) 1779 result.add(item); 1780 } else 1781 getChildrenByName(item, exp.getName(), result); 1782 if (result.size() == 0 && atEntry && context.appInfo != null) { 1783 Base temp = hostServices.resolveConstant(context.appInfo, exp.getName()); 1784 if (temp != null) { 1785 result.add(temp); 1786 } 1787 } 1788 return result; 1789 } 1790 1791 private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException { 1792 if (hostServices == null) 1793 throw new PathEngineException("Unable to resolve context reference since no host services are provided"); 1794 return hostServices.resolveConstantType(context.appInfo, name); 1795 } 1796 1797 private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException { 1798 if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up 1799 return new TypeDetails(CollectionStatus.SINGLETON, type); 1800 TypeDetails result = new TypeDetails(null); 1801 getChildTypesByName(type, exp.getName(), result); 1802 return result; 1803 } 1804 1805 1806 private String hashTail(String type) { 1807 return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); 1808 } 1809 1810 1811 @SuppressWarnings("unchecked") 1812 private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException { 1813 List<TypeDetails> paramTypes = new ArrayList<TypeDetails>(); 1814 if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As) 1815 paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, "string")); 1816 else 1817 for (ExpressionNode expr : exp.getParameters()) { 1818 if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat) 1819 paramTypes.add(executeType(changeThis(context, focus), focus, expr, true)); 1820 else 1821 paramTypes.add(executeType(context, focus, expr, true)); 1822 } 1823 switch (exp.getFunction()) { 1824 case Empty : 1825 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1826 case Not : 1827 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1828 case Exists : 1829 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1830 case SubsetOf : { 1831 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 1832 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1833 } 1834 case SupersetOf : { 1835 checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 1836 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1837 } 1838 case IsDistinct : 1839 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1840 case Distinct : 1841 return focus; 1842 case Count : 1843 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1844 case Where : 1845 return focus; 1846 case Select : 1847 return anything(focus.getCollectionStatus()); 1848 case All : 1849 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1850 case Repeat : 1851 return anything(focus.getCollectionStatus()); 1852 case Item : { 1853 checkOrdered(focus, "item"); 1854 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1855 return focus; 1856 } 1857 case As : { 1858 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1859 return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName()); 1860 } 1861 case Is : { 1862 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1863 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1864 } 1865 case Single : 1866 return focus.toSingleton(); 1867 case First : { 1868 checkOrdered(focus, "first"); 1869 return focus.toSingleton(); 1870 } 1871 case Last : { 1872 checkOrdered(focus, "last"); 1873 return focus.toSingleton(); 1874 } 1875 case Tail : { 1876 checkOrdered(focus, "tail"); 1877 return focus; 1878 } 1879 case Skip : { 1880 checkOrdered(focus, "skip"); 1881 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1882 return focus; 1883 } 1884 case Take : { 1885 checkOrdered(focus, "take"); 1886 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1887 return focus; 1888 } 1889 case Iif : { 1890 TypeDetails types = new TypeDetails(null); 1891 types.update(paramTypes.get(0)); 1892 if (paramTypes.size() > 1) 1893 types.update(paramTypes.get(1)); 1894 return types; 1895 } 1896 case ToInteger : { 1897 checkContextPrimitive(focus, "toInteger"); 1898 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1899 } 1900 case ToDecimal : { 1901 checkContextPrimitive(focus, "toDecimal"); 1902 return new TypeDetails(CollectionStatus.SINGLETON, "decimal"); 1903 } 1904 case ToString : { 1905 checkContextPrimitive(focus, "toString"); 1906 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1907 } 1908 case Substring : { 1909 checkContextString(focus, "subString"); 1910 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"), new TypeDetails(CollectionStatus.SINGLETON, "integer")); 1911 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1912 } 1913 case StartsWith : { 1914 checkContextString(focus, "startsWith"); 1915 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1916 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1917 } 1918 case EndsWith : { 1919 checkContextString(focus, "endsWith"); 1920 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1921 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1922 } 1923 case Matches : { 1924 checkContextString(focus, "matches"); 1925 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1926 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1927 } 1928 case ReplaceMatches : { 1929 checkContextString(focus, "replaceMatches"); 1930 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string")); 1931 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1932 } 1933 case Contains : { 1934 checkContextString(focus, "contains"); 1935 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1936 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1937 } 1938 case Replace : { 1939 checkContextString(focus, "replace"); 1940 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string")); 1941 return new TypeDetails(CollectionStatus.SINGLETON, "string"); 1942 } 1943 case Length : { 1944 checkContextPrimitive(focus, "length"); 1945 return new TypeDetails(CollectionStatus.SINGLETON, "integer"); 1946 } 1947 case Children : 1948 return childTypes(focus, "*"); 1949 case Descendants : 1950 return childTypes(focus, "**"); 1951 case MemberOf : { 1952 checkContextCoded(focus, "memberOf"); 1953 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1954 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1955 } 1956 case Trace : { 1957 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1958 return focus; 1959 } 1960 case Today : 1961 return new TypeDetails(CollectionStatus.SINGLETON, "date"); 1962 case Now : 1963 return new TypeDetails(CollectionStatus.SINGLETON, "dateTime"); 1964 case Resolve : { 1965 checkContextReference(focus, "resolve"); 1966 return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 1967 } 1968 case Extension : { 1969 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1970 return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 1971 } 1972 case HasValue : 1973 return new TypeDetails(CollectionStatus.SINGLETON, "boolean"); 1974 case Alias : 1975 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1976 return anything(CollectionStatus.SINGLETON); 1977 case AliasAs : 1978 checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string")); 1979 return focus; 1980 case Custom : { 1981 return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes); 1982 } 1983 default: 1984 break; 1985 } 1986 throw new Error("not Implemented yet"); 1987 } 1988 1989 1990 private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException { 1991 int i = 0; 1992 for (TypeDetails pt : typeSet) { 1993 if (i == paramTypes.size()) 1994 return; 1995 TypeDetails actual = paramTypes.get(i); 1996 i++; 1997 for (String a : actual.getTypes()) { 1998 if (!pt.hasType(worker, a)) 1999 throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 2000 } 2001 } 2002 } 2003 2004 private void checkOrdered(TypeDetails focus, String name) throws PathEngineException { 2005 if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) 2006 throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 2007 } 2008 2009 private void checkContextReference(TypeDetails focus, String name) throws PathEngineException { 2010 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference")) 2011 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, Reference"); 2012 } 2013 2014 2015 private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException { 2016 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) 2017 throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept"); 2018 } 2019 2020 2021 private void checkContextString(TypeDetails focus, String name) throws PathEngineException { 2022 if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "id")) 2023 throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 2024 } 2025 2026 2027 private void checkContextPrimitive(TypeDetails focus, String name) throws PathEngineException { 2028 if (!focus.hasType(primitiveTypes)) 2029 throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()); 2030 } 2031 2032 2033 private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException { 2034 TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED); 2035 for (String f : focus.getTypes()) 2036 getChildTypesByName(f, mask, result); 2037 return result; 2038 } 2039 2040 private TypeDetails anything(CollectionStatus status) { 2041 return new TypeDetails(status, allTypes.keySet()); 2042 } 2043 2044 // private boolean isPrimitiveType(String s) { 2045 // 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"); 2046 // } 2047 2048 private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2049 switch (exp.getFunction()) { 2050 case Empty : return funcEmpty(context, focus, exp); 2051 case Not : return funcNot(context, focus, exp); 2052 case Exists : return funcExists(context, focus, exp); 2053 case SubsetOf : return funcSubsetOf(context, focus, exp); 2054 case SupersetOf : return funcSupersetOf(context, focus, exp); 2055 case IsDistinct : return funcIsDistinct(context, focus, exp); 2056 case Distinct : return funcDistinct(context, focus, exp); 2057 case Count : return funcCount(context, focus, exp); 2058 case Where : return funcWhere(context, focus, exp); 2059 case Select : return funcSelect(context, focus, exp); 2060 case All : return funcAll(context, focus, exp); 2061 case Repeat : return funcRepeat(context, focus, exp); 2062 case Item : return funcItem(context, focus, exp); 2063 case As : return funcAs(context, focus, exp); 2064 case Is : return funcIs(context, focus, exp); 2065 case Single : return funcSingle(context, focus, exp); 2066 case First : return funcFirst(context, focus, exp); 2067 case Last : return funcLast(context, focus, exp); 2068 case Tail : return funcTail(context, focus, exp); 2069 case Skip : return funcSkip(context, focus, exp); 2070 case Take : return funcTake(context, focus, exp); 2071 case Iif : return funcIif(context, focus, exp); 2072 case ToInteger : return funcToInteger(context, focus, exp); 2073 case ToDecimal : return funcToDecimal(context, focus, exp); 2074 case ToString : return funcToString(context, focus, exp); 2075 case Substring : return funcSubstring(context, focus, exp); 2076 case StartsWith : return funcStartsWith(context, focus, exp); 2077 case EndsWith : return funcEndsWith(context, focus, exp); 2078 case Matches : return funcMatches(context, focus, exp); 2079 case ReplaceMatches : return funcReplaceMatches(context, focus, exp); 2080 case Contains : return funcContains(context, focus, exp); 2081 case Replace : return funcReplace(context, focus, exp); 2082 case Length : return funcLength(context, focus, exp); 2083 case Children : return funcChildren(context, focus, exp); 2084 case Descendants : return funcDescendants(context, focus, exp); 2085 case MemberOf : return funcMemberOf(context, focus, exp); 2086 case Trace : return funcTrace(context, focus, exp); 2087 case Today : return funcToday(context, focus, exp); 2088 case Now : return funcNow(context, focus, exp); 2089 case Resolve : return funcResolve(context, focus, exp); 2090 case Extension : return funcExtension(context, focus, exp); 2091 case HasValue : return funcHasValue(context, focus, exp); 2092 case AliasAs : return funcAliasAs(context, focus, exp); 2093 case Alias : return funcAlias(context, focus, exp); 2094 case Custom: { 2095 List<List<Base>> params = new ArrayList<List<Base>>(); 2096 for (ExpressionNode p : exp.getParameters()) 2097 params.add(execute(context, focus, p, true)); 2098 return hostServices.executeFunction(context.appInfo, exp.getName(), params); 2099 } 2100 default: 2101 throw new Error("not Implemented yet"); 2102 } 2103 } 2104 2105 private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2106 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2107 String name = nl.get(0).primitiveValue(); 2108 context.addAlias(name, focus); 2109 return focus; 2110 } 2111 2112 private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2113 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2114 String name = nl.get(0).primitiveValue(); 2115 List<Base> res = new ArrayList<Base>(); 2116 Base b = context.getAlias(name); 2117 if (b != null) 2118 res.add(b); 2119 return res; 2120 2121 } 2122 2123 private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2124 if (exp.getParameters().size() == 1) { 2125 List<Base> result = new ArrayList<Base>(); 2126 List<Base> pc = new ArrayList<Base>(); 2127 boolean all = true; 2128 for (Base item : focus) { 2129 pc.clear(); 2130 pc.add(item); 2131 if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) { 2132 all = false; 2133 break; 2134 } 2135 } 2136 result.add(new BooleanType(all)); 2137 return result; 2138 } else {// (exp.getParameters().size() == 0) { 2139 List<Base> result = new ArrayList<Base>(); 2140 boolean all = true; 2141 for (Base item : focus) { 2142 boolean v = false; 2143 if (item instanceof BooleanType) { 2144 v = ((BooleanType) item).booleanValue(); 2145 } else 2146 v = item != null; 2147 if (!v) { 2148 all = false; 2149 break; 2150 } 2151 } 2152 result.add(new BooleanType(all)); 2153 return result; 2154 } 2155 } 2156 2157 2158 private ExecutionContext changeThis(ExecutionContext context, Base newThis) { 2159 return new ExecutionContext(context.appInfo, context.resource, context.context, context.aliases, newThis); 2160 } 2161 2162 private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) { 2163 return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis); 2164 } 2165 2166 2167 private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2168 List<Base> result = new ArrayList<Base>(); 2169 result.add(DateTimeType.now()); 2170 return result; 2171 } 2172 2173 2174 private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2175 List<Base> result = new ArrayList<Base>(); 2176 result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY)); 2177 return result; 2178 } 2179 2180 2181 private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2182 throw new Error("not Implemented yet"); 2183 } 2184 2185 2186 private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2187 List<Base> result = new ArrayList<Base>(); 2188 List<Base> current = new ArrayList<Base>(); 2189 current.addAll(focus); 2190 List<Base> added = new ArrayList<Base>(); 2191 boolean more = true; 2192 while (more) { 2193 added.clear(); 2194 for (Base item : current) { 2195 getChildrenByName(item, "*", added); 2196 } 2197 more = !added.isEmpty(); 2198 result.addAll(added); 2199 current.clear(); 2200 current.addAll(added); 2201 } 2202 return result; 2203 } 2204 2205 2206 private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2207 List<Base> result = new ArrayList<Base>(); 2208 for (Base b : focus) 2209 getChildrenByName(b, "*", result); 2210 return result; 2211 } 2212 2213 2214 private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException { 2215 List<Base> result = new ArrayList<Base>(); 2216 2217 if (focus.size() == 1) { 2218 String f = convertToString(focus.get(0)); 2219 2220 if (!Utilities.noString(f)) { 2221 2222 if (exp.getParameters().size() == 2) { 2223 2224 String t = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2225 String r = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2226 2227 String n = f.replace(t, r); 2228 result.add(new StringType(n)); 2229 } 2230 else { 2231 throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size())); 2232 } 2233 } 2234 else { 2235 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item")); 2236 } 2237 } 2238 else { 2239 throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size())); 2240 } 2241 return result; 2242 } 2243 2244 2245 private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2246 List<Base> result = new ArrayList<Base>(); 2247 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2248 2249 if (focus.size() == 1 && !Utilities.noString(sw)) 2250 result.add(new BooleanType(convertToString(focus.get(0)).contains(sw))); 2251 else 2252 result.add(new BooleanType(false)); 2253 return result; 2254 } 2255 2256 2257 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2258 List<Base> result = new ArrayList<Base>(); 2259 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2260 2261 if (focus.size() == 1 && !Utilities.noString(sw)) 2262 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw))); 2263 else 2264 result.add(new BooleanType(false)); 2265 return result; 2266 } 2267 2268 2269 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2270 List<Base> result = new ArrayList<Base>(); 2271 result.add(new StringType(convertToString(focus))); 2272 return result; 2273 } 2274 2275 2276 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2277 String s = convertToString(focus); 2278 List<Base> result = new ArrayList<Base>(); 2279 if (Utilities.isDecimal(s, true)) 2280 result.add(new DecimalType(s)); 2281 return result; 2282 } 2283 2284 2285 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2286 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2287 Boolean v = convertToBoolean(n1); 2288 2289 if (v) 2290 return execute(context, focus, exp.getParameters().get(1), true); 2291 else if (exp.getParameters().size() < 3) 2292 return new ArrayList<Base>(); 2293 else 2294 return execute(context, focus, exp.getParameters().get(2), true); 2295 } 2296 2297 2298 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2299 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2300 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2301 2302 List<Base> result = new ArrayList<Base>(); 2303 for (int i = 0; i < Math.min(focus.size(), i1); i++) 2304 result.add(focus.get(i)); 2305 return result; 2306 } 2307 2308 2309 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2310 if (focus.size() == 1) 2311 return focus; 2312 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 2313 } 2314 2315 2316 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2317 List<Base> result = new ArrayList<Base>(); 2318 if (focus.size() == 0 || focus.size() > 1) 2319 result.add(new BooleanType(false)); 2320 else { 2321 String tn = exp.getParameters().get(0).getName(); 2322 result.add(new BooleanType(focus.get(0).hasType(tn))); 2323 } 2324 return result; 2325 } 2326 2327 2328 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2329 List<Base> result = new ArrayList<Base>(); 2330 String tn = exp.getParameters().get(0).getName(); 2331 for (Base b : focus) 2332 if (b.hasType(tn)) 2333 result.add(b); 2334 return result; 2335 } 2336 2337 2338 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2339 List<Base> result = new ArrayList<Base>(); 2340 List<Base> current = new ArrayList<Base>(); 2341 current.addAll(focus); 2342 List<Base> added = new ArrayList<Base>(); 2343 boolean more = true; 2344 while (more) { 2345 added.clear(); 2346 List<Base> pc = new ArrayList<Base>(); 2347 for (Base item : current) { 2348 pc.clear(); 2349 pc.add(item); 2350 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 2351 } 2352 more = !added.isEmpty(); 2353 result.addAll(added); 2354 current.clear(); 2355 current.addAll(added); 2356 } 2357 return result; 2358 } 2359 2360 2361 2362 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2363 if (focus.size() <= 1) 2364 return makeBoolean(true); 2365 2366 boolean distinct = true; 2367 for (int i = 0; i < focus.size(); i++) { 2368 for (int j = i+1; j < focus.size(); j++) { 2369 if (doEquals(focus.get(j), focus.get(i))) { 2370 distinct = false; 2371 break; 2372 } 2373 } 2374 } 2375 return makeBoolean(distinct); 2376 } 2377 2378 2379 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2380 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2381 2382 boolean valid = true; 2383 for (Base item : target) { 2384 boolean found = false; 2385 for (Base t : focus) { 2386 if (Base.compareDeep(item, t, false)) { 2387 found = true; 2388 break; 2389 } 2390 } 2391 if (!found) { 2392 valid = false; 2393 break; 2394 } 2395 } 2396 List<Base> result = new ArrayList<Base>(); 2397 result.add(new BooleanType(valid)); 2398 return result; 2399 } 2400 2401 2402 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2403 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2404 2405 boolean valid = true; 2406 for (Base item : focus) { 2407 boolean found = false; 2408 for (Base t : target) { 2409 if (Base.compareDeep(item, t, false)) { 2410 found = true; 2411 break; 2412 } 2413 } 2414 if (!found) { 2415 valid = false; 2416 break; 2417 } 2418 } 2419 List<Base> result = new ArrayList<Base>(); 2420 result.add(new BooleanType(valid)); 2421 return result; 2422 } 2423 2424 2425 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2426 List<Base> result = new ArrayList<Base>(); 2427 result.add(new BooleanType(!ElementUtil.isEmpty(focus))); 2428 return result; 2429 } 2430 2431 2432 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2433 List<Base> result = new ArrayList<Base>(); 2434 for (Base item : focus) { 2435 String s = convertToString(item); 2436 if (item.fhirType().equals("Reference")) { 2437 Property p = item.getChildByName("reference"); 2438 if (p.hasValues()) 2439 s = convertToString(p.getValues().get(0)); 2440 } 2441 Base res = null; 2442 if (s.startsWith("#")) { 2443 Property p = context.resource.getChildByName("contained"); 2444 for (Base c : p.getValues()) { 2445 if (s.equals(c.getIdBase())) 2446 res = c; 2447 } 2448 } else if (hostServices != null) { 2449 res = hostServices.resolveReference(context.appInfo, s); 2450 } 2451 if (res != null) 2452 result.add(res); 2453 } 2454 return result; 2455 } 2456 2457 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2458 List<Base> result = new ArrayList<Base>(); 2459 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2460 String url = nl.get(0).primitiveValue(); 2461 2462 for (Base item : focus) { 2463 List<Base> ext = new ArrayList<Base>(); 2464 getChildrenByName(item, "extension", ext); 2465 getChildrenByName(item, "modifierExtension", ext); 2466 for (Base ex : ext) { 2467 List<Base> vl = new ArrayList<Base>(); 2468 getChildrenByName(ex, "url", vl); 2469 if (convertToString(vl).equals(url)) 2470 result.add(ex); 2471 } 2472 } 2473 return result; 2474 } 2475 2476 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2477 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2478 String name = nl.get(0).primitiveValue(); 2479 2480 log(name, focus); 2481 return focus; 2482 } 2483 2484 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2485 if (focus.size() <= 1) 2486 return focus; 2487 2488 List<Base> result = new ArrayList<Base>(); 2489 for (int i = 0; i < focus.size(); i++) { 2490 boolean found = false; 2491 for (int j = i+1; j < focus.size(); j++) { 2492 if (doEquals(focus.get(j), focus.get(i))) { 2493 found = true; 2494 break; 2495 } 2496 } 2497 if (!found) 2498 result.add(focus.get(i)); 2499 } 2500 return result; 2501 } 2502 2503 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2504 List<Base> result = new ArrayList<Base>(); 2505 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2506 2507 if (focus.size() == 1 && !Utilities.noString(sw)) { 2508 String st = convertToString(focus.get(0)); 2509 if (Utilities.noString(st)) 2510 result.add(new BooleanType(false)); 2511 else 2512 result.add(new BooleanType(st.matches(sw))); 2513 } else 2514 result.add(new BooleanType(false)); 2515 return result; 2516 } 2517 2518 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2519 List<Base> result = new ArrayList<Base>(); 2520 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2521 2522 if (focus.size() == 1 && !Utilities.noString(sw)) { 2523 String st = convertToString(focus.get(0)); 2524 if (Utilities.noString(st)) 2525 result.add(new BooleanType(false)); 2526 else 2527 result.add(new BooleanType(st.contains(sw))); 2528 } else 2529 result.add(new BooleanType(false)); 2530 return result; 2531 } 2532 2533 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2534 List<Base> result = new ArrayList<Base>(); 2535 if (focus.size() == 1) { 2536 String s = convertToString(focus.get(0)); 2537 result.add(new IntegerType(s.length())); 2538 } 2539 return result; 2540 } 2541 2542 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2543 List<Base> result = new ArrayList<Base>(); 2544 if (focus.size() == 1) { 2545 String s = convertToString(focus.get(0)); 2546 result.add(new BooleanType(!Utilities.noString(s))); 2547 } 2548 return result; 2549 } 2550 2551 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2552 List<Base> result = new ArrayList<Base>(); 2553 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2554 2555 if (focus.size() == 1 && !Utilities.noString(sw)) 2556 result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw))); 2557 else 2558 result.add(new BooleanType(false)); 2559 return result; 2560 } 2561 2562 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2563 List<Base> result = new ArrayList<Base>(); 2564 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2565 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2566 int i2 = -1; 2567 if (exp.parameterCount() == 2) { 2568 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 2569 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 2570 } 2571 2572 if (focus.size() == 1) { 2573 String sw = convertToString(focus.get(0)); 2574 String s; 2575 if (i1 < 0 || i1 >= sw.length()) 2576 return new ArrayList<Base>(); 2577 if (exp.parameterCount() == 2) 2578 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 2579 else 2580 s = sw.substring(i1); 2581 if (!Utilities.noString(s)) 2582 result.add(new StringType(s)); 2583 } 2584 return result; 2585 } 2586 2587 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2588 String s = convertToString(focus); 2589 List<Base> result = new ArrayList<Base>(); 2590 if (Utilities.isInteger(s)) 2591 result.add(new IntegerType(s)); 2592 return result; 2593 } 2594 2595 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2596 List<Base> result = new ArrayList<Base>(); 2597 result.add(new IntegerType(focus.size())); 2598 return result; 2599 } 2600 2601 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2602 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2603 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2604 2605 List<Base> result = new ArrayList<Base>(); 2606 for (int i = i1; i < focus.size(); i++) 2607 result.add(focus.get(i)); 2608 return result; 2609 } 2610 2611 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2612 List<Base> result = new ArrayList<Base>(); 2613 for (int i = 1; i < focus.size(); i++) 2614 result.add(focus.get(i)); 2615 return result; 2616 } 2617 2618 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2619 List<Base> result = new ArrayList<Base>(); 2620 if (focus.size() > 0) 2621 result.add(focus.get(focus.size()-1)); 2622 return result; 2623 } 2624 2625 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2626 List<Base> result = new ArrayList<Base>(); 2627 if (focus.size() > 0) 2628 result.add(focus.get(0)); 2629 return result; 2630 } 2631 2632 2633 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2634 List<Base> result = new ArrayList<Base>(); 2635 List<Base> pc = new ArrayList<Base>(); 2636 for (Base item : focus) { 2637 pc.clear(); 2638 pc.add(item); 2639 if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) 2640 result.add(item); 2641 } 2642 return result; 2643 } 2644 2645 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2646 List<Base> result = new ArrayList<Base>(); 2647 List<Base> pc = new ArrayList<Base>(); 2648 for (Base item : focus) { 2649 pc.clear(); 2650 pc.add(item); 2651 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 2652 } 2653 return result; 2654 } 2655 2656 2657 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2658 List<Base> result = new ArrayList<Base>(); 2659 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2660 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 2661 result.add(focus.get(Integer.parseInt(s))); 2662 return result; 2663 } 2664 2665 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2666 List<Base> result = new ArrayList<Base>(); 2667 result.add(new BooleanType(ElementUtil.isEmpty(focus))); 2668 return result; 2669 } 2670 2671 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2672 return makeBoolean(!convertToBoolean(focus)); 2673 } 2674 2675 public class ElementDefinitionMatch { 2676 private ElementDefinition definition; 2677 private String fixedType; 2678 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 2679 super(); 2680 this.definition = definition; 2681 this.fixedType = fixedType; 2682 } 2683 public ElementDefinition getDefinition() { 2684 return definition; 2685 } 2686 public String getFixedType() { 2687 return fixedType; 2688 } 2689 2690 } 2691 2692 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 2693 if (Utilities.noString(type)) 2694 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 2695 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) 2696 return; 2697 String url = null; 2698 if (type.contains("#")) { 2699 url = type.substring(0, type.indexOf("#")); 2700 } else { 2701 url = type; 2702 } 2703 String tail = ""; 2704 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 2705 if (sd == null) 2706 throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 2707 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 2708 ElementDefinitionMatch m = null; 2709 if (type.contains("#")) 2710 m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false); 2711 if (m != null && hasDataType(m.definition)) { 2712 if (m.fixedType != null) 2713 { 2714 StructureDefinition dt = worker.fetchTypeDefinition(m.fixedType); 2715 if (dt == null) 2716 throw new DefinitionException("unknown data type "+m.fixedType); 2717 sdl.add(dt); 2718 } else 2719 for (TypeRefComponent t : m.definition.getType()) { 2720 StructureDefinition dt = worker.fetchTypeDefinition(t.getCode()); 2721 if (dt == null) 2722 throw new DefinitionException("unknown data type "+t.getCode()); 2723 sdl.add(dt); 2724 } 2725 } else { 2726 sdl.add(sd); 2727 if (type.contains("#")) { 2728 tail = type.substring(type.indexOf("#")+1); 2729 tail = tail.substring(tail.indexOf(".")); 2730 } 2731 } 2732 2733 for (StructureDefinition sdi : sdl) { 2734 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 2735 if (name.equals("**")) { 2736 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2737 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2738 if (ed.getPath().startsWith(path)) 2739 for (TypeRefComponent t : ed.getType()) { 2740 if (t.hasCode() && t.getCodeElement().hasValue()) { 2741 String tn = null; 2742 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2743 tn = sdi.getType()+"#"+ed.getPath(); 2744 else 2745 tn = t.getCode(); 2746 if (t.getCode().equals("Resource")) { 2747 for (String rn : worker.getResourceNames()) { 2748 if (!result.hasType(worker, rn)) { 2749 getChildTypesByName(result.addType(rn), "**", result); 2750 } 2751 } 2752 } else if (!result.hasType(worker, tn)) { 2753 getChildTypesByName(result.addType(tn), "**", result); 2754 } 2755 } 2756 } 2757 } 2758 } else if (name.equals("*")) { 2759 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2760 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2761 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 2762 for (TypeRefComponent t : ed.getType()) { 2763 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2764 result.addType(sdi.getType()+"#"+ed.getPath()); 2765 else if (t.getCode().equals("Resource")) 2766 result.addTypes(worker.getResourceNames()); 2767 else 2768 result.addType(t.getCode()); 2769 } 2770 } 2771 } else { 2772 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 2773 2774 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 2775 if (ed != null) { 2776 if (!Utilities.noString(ed.getFixedType())) 2777 result.addType(ed.getFixedType()); 2778 else 2779 for (TypeRefComponent t : ed.getDefinition().getType()) { 2780 if (Utilities.noString(t.getCode())) 2781 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 2782 2783 ProfiledType pt = null; 2784 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2785 pt = new ProfiledType(sdi.getUrl()+"#"+path); 2786 else if (t.getCode().equals("Resource")) 2787 result.addTypes(worker.getResourceNames()); 2788 else 2789 pt = new ProfiledType(t.getCode()); 2790 if (pt != null) { 2791 if (t.hasProfile()) 2792 pt.addProfile(t.getProfile()); 2793 if (ed.getDefinition().hasBinding()) 2794 pt.addBinding(ed.getDefinition().getBinding()); 2795 result.addType(pt); 2796 } 2797 } 2798 } 2799 } 2800 } 2801 } 2802 2803 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 2804 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2805 if (ed.getPath().equals(path)) { 2806 if (ed.hasContentReference()) { 2807 return getElementDefinitionById(sd, ed.getContentReference()); 2808 } else 2809 return new ElementDefinitionMatch(ed, null); 2810 } 2811 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) 2812 return new ElementDefinitionMatch(ed, null); 2813 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 2814 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 2815 if (primitiveTypes.contains(s)) 2816 return new ElementDefinitionMatch(ed, s); 2817 else 2818 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); 2819 } 2820 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 2821 // now we walk into the type. 2822 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 2823 throw new PathEngineException("Internal typing issue...."); 2824 StructureDefinition nsd = worker.fetchTypeDefinition(ed.getType().get(0).getCode()); 2825 if (nsd == null) 2826 throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode()); 2827 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName); 2828 } 2829 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 2830 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 2831 return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName); 2832 } 2833 } 2834 return null; 2835 } 2836 2837 private boolean isAbstractType(List<TypeRefComponent> list) { 2838 return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 2839} 2840 2841 2842 private boolean hasType(ElementDefinition ed, String s) { 2843 for (TypeRefComponent t : ed.getType()) 2844 if (s.equalsIgnoreCase(t.getCode())) 2845 return true; 2846 return false; 2847 } 2848 2849 private boolean hasDataType(ElementDefinition ed) { 2850 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 2851 } 2852 2853 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 2854 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2855 if (ref.equals("#"+ed.getId())) 2856 return new ElementDefinitionMatch(ed, null); 2857 } 2858 return null; 2859 } 2860 2861 2862 public boolean hasLog() { 2863 return log != null && log.length() > 0; 2864 } 2865 2866 2867 public String takeLog() { 2868 if (!hasLog()) 2869 return ""; 2870 String s = log.toString(); 2871 log = new StringBuilder(); 2872 return s; 2873 } 2874 2875}