
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 regex = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2248 String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true)); 2249 2250 if (focus.size() == 1 && !Utilities.noString(regex)) { 2251 result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl))); 2252 } else { 2253 result.add(new StringType(convertToString(focus.get(0)))); 2254 } 2255 return result; 2256 } 2257 2258 2259 private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2260 List<Base> result = new ArrayList<Base>(); 2261 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2262 2263 if (focus.size() == 1 && !Utilities.noString(sw)) 2264 result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw))); 2265 else 2266 result.add(new BooleanType(false)); 2267 return result; 2268 } 2269 2270 2271 private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2272 List<Base> result = new ArrayList<Base>(); 2273 result.add(new StringType(convertToString(focus))); 2274 return result; 2275 } 2276 2277 2278 private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2279 String s = convertToString(focus); 2280 List<Base> result = new ArrayList<Base>(); 2281 if (Utilities.isDecimal(s, true)) 2282 result.add(new DecimalType(s)); 2283 return result; 2284 } 2285 2286 2287 private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2288 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2289 Boolean v = convertToBoolean(n1); 2290 2291 if (v) 2292 return execute(context, focus, exp.getParameters().get(1), true); 2293 else if (exp.getParameters().size() < 3) 2294 return new ArrayList<Base>(); 2295 else 2296 return execute(context, focus, exp.getParameters().get(2), true); 2297 } 2298 2299 2300 private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2301 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2302 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2303 2304 List<Base> result = new ArrayList<Base>(); 2305 for (int i = 0; i < Math.min(focus.size(), i1); i++) 2306 result.add(focus.get(i)); 2307 return result; 2308 } 2309 2310 2311 private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2312 if (focus.size() == 1) 2313 return focus; 2314 throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size())); 2315 } 2316 2317 2318 private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException { 2319 List<Base> result = new ArrayList<Base>(); 2320 if (focus.size() == 0 || focus.size() > 1) 2321 result.add(new BooleanType(false)); 2322 else { 2323 String tn = exp.getParameters().get(0).getName(); 2324 result.add(new BooleanType(focus.get(0).hasType(tn))); 2325 } 2326 return result; 2327 } 2328 2329 2330 private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2331 List<Base> result = new ArrayList<Base>(); 2332 String tn = exp.getParameters().get(0).getName(); 2333 for (Base b : focus) 2334 if (b.hasType(tn)) 2335 result.add(b); 2336 return result; 2337 } 2338 2339 2340 private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2341 List<Base> result = new ArrayList<Base>(); 2342 List<Base> current = new ArrayList<Base>(); 2343 current.addAll(focus); 2344 List<Base> added = new ArrayList<Base>(); 2345 boolean more = true; 2346 while (more) { 2347 added.clear(); 2348 List<Base> pc = new ArrayList<Base>(); 2349 for (Base item : current) { 2350 pc.clear(); 2351 pc.add(item); 2352 added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false)); 2353 } 2354 more = !added.isEmpty(); 2355 result.addAll(added); 2356 current.clear(); 2357 current.addAll(added); 2358 } 2359 return result; 2360 } 2361 2362 2363 2364 private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2365 if (focus.size() <= 1) 2366 return makeBoolean(true); 2367 2368 boolean distinct = true; 2369 for (int i = 0; i < focus.size(); i++) { 2370 for (int j = i+1; j < focus.size(); j++) { 2371 if (doEquals(focus.get(j), focus.get(i))) { 2372 distinct = false; 2373 break; 2374 } 2375 } 2376 } 2377 return makeBoolean(distinct); 2378 } 2379 2380 2381 private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2382 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2383 2384 boolean valid = true; 2385 for (Base item : target) { 2386 boolean found = false; 2387 for (Base t : focus) { 2388 if (Base.compareDeep(item, t, false)) { 2389 found = true; 2390 break; 2391 } 2392 } 2393 if (!found) { 2394 valid = false; 2395 break; 2396 } 2397 } 2398 List<Base> result = new ArrayList<Base>(); 2399 result.add(new BooleanType(valid)); 2400 return result; 2401 } 2402 2403 2404 private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2405 List<Base> target = execute(context, focus, exp.getParameters().get(0), true); 2406 2407 boolean valid = true; 2408 for (Base item : focus) { 2409 boolean found = false; 2410 for (Base t : target) { 2411 if (Base.compareDeep(item, t, false)) { 2412 found = true; 2413 break; 2414 } 2415 } 2416 if (!found) { 2417 valid = false; 2418 break; 2419 } 2420 } 2421 List<Base> result = new ArrayList<Base>(); 2422 result.add(new BooleanType(valid)); 2423 return result; 2424 } 2425 2426 2427 private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2428 List<Base> result = new ArrayList<Base>(); 2429 result.add(new BooleanType(!ElementUtil.isEmpty(focus))); 2430 return result; 2431 } 2432 2433 2434 private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2435 List<Base> result = new ArrayList<Base>(); 2436 for (Base item : focus) { 2437 String s = convertToString(item); 2438 if (item.fhirType().equals("Reference")) { 2439 Property p = item.getChildByName("reference"); 2440 if (p.hasValues()) 2441 s = convertToString(p.getValues().get(0)); 2442 } 2443 Base res = null; 2444 if (s.startsWith("#")) { 2445 Property p = context.resource.getChildByName("contained"); 2446 for (Base c : p.getValues()) { 2447 if (s.equals(c.getIdBase())) 2448 res = c; 2449 } 2450 } else if (hostServices != null) { 2451 res = hostServices.resolveReference(context.appInfo, s); 2452 } 2453 if (res != null) 2454 result.add(res); 2455 } 2456 return result; 2457 } 2458 2459 private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2460 List<Base> result = new ArrayList<Base>(); 2461 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2462 String url = nl.get(0).primitiveValue(); 2463 2464 for (Base item : focus) { 2465 List<Base> ext = new ArrayList<Base>(); 2466 getChildrenByName(item, "extension", ext); 2467 getChildrenByName(item, "modifierExtension", ext); 2468 for (Base ex : ext) { 2469 List<Base> vl = new ArrayList<Base>(); 2470 getChildrenByName(ex, "url", vl); 2471 if (convertToString(vl).equals(url)) 2472 result.add(ex); 2473 } 2474 } 2475 return result; 2476 } 2477 2478 private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2479 List<Base> nl = execute(context, focus, exp.getParameters().get(0), true); 2480 String name = nl.get(0).primitiveValue(); 2481 2482 log(name, focus); 2483 return focus; 2484 } 2485 2486 private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2487 if (focus.size() <= 1) 2488 return focus; 2489 2490 List<Base> result = new ArrayList<Base>(); 2491 for (int i = 0; i < focus.size(); i++) { 2492 boolean found = false; 2493 for (int j = i+1; j < focus.size(); j++) { 2494 if (doEquals(focus.get(j), focus.get(i))) { 2495 found = true; 2496 break; 2497 } 2498 } 2499 if (!found) 2500 result.add(focus.get(i)); 2501 } 2502 return result; 2503 } 2504 2505 private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2506 List<Base> result = new ArrayList<Base>(); 2507 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2508 2509 if (focus.size() == 1 && !Utilities.noString(sw)) { 2510 String st = convertToString(focus.get(0)); 2511 if (Utilities.noString(st)) 2512 result.add(new BooleanType(false)); 2513 else 2514 result.add(new BooleanType(st.matches(sw))); 2515 } else 2516 result.add(new BooleanType(false)); 2517 return result; 2518 } 2519 2520 private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2521 List<Base> result = new ArrayList<Base>(); 2522 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2523 2524 if (focus.size() == 1 && !Utilities.noString(sw)) { 2525 String st = convertToString(focus.get(0)); 2526 if (Utilities.noString(st)) 2527 result.add(new BooleanType(false)); 2528 else 2529 result.add(new BooleanType(st.contains(sw))); 2530 } else 2531 result.add(new BooleanType(false)); 2532 return result; 2533 } 2534 2535 private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2536 List<Base> result = new ArrayList<Base>(); 2537 if (focus.size() == 1) { 2538 String s = convertToString(focus.get(0)); 2539 result.add(new IntegerType(s.length())); 2540 } 2541 return result; 2542 } 2543 2544 private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2545 List<Base> result = new ArrayList<Base>(); 2546 if (focus.size() == 1) { 2547 String s = convertToString(focus.get(0)); 2548 result.add(new BooleanType(!Utilities.noString(s))); 2549 } 2550 return result; 2551 } 2552 2553 private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2554 List<Base> result = new ArrayList<Base>(); 2555 String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2556 2557 if (focus.size() == 1 && !Utilities.noString(sw)) 2558 result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw))); 2559 else 2560 result.add(new BooleanType(false)); 2561 return result; 2562 } 2563 2564 private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2565 List<Base> result = new ArrayList<Base>(); 2566 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2567 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2568 int i2 = -1; 2569 if (exp.parameterCount() == 2) { 2570 List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true); 2571 i2 = Integer.parseInt(n2.get(0).primitiveValue()); 2572 } 2573 2574 if (focus.size() == 1) { 2575 String sw = convertToString(focus.get(0)); 2576 String s; 2577 if (i1 < 0 || i1 >= sw.length()) 2578 return new ArrayList<Base>(); 2579 if (exp.parameterCount() == 2) 2580 s = sw.substring(i1, Math.min(sw.length(), i1+i2)); 2581 else 2582 s = sw.substring(i1); 2583 if (!Utilities.noString(s)) 2584 result.add(new StringType(s)); 2585 } 2586 return result; 2587 } 2588 2589 private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2590 String s = convertToString(focus); 2591 List<Base> result = new ArrayList<Base>(); 2592 if (Utilities.isInteger(s)) 2593 result.add(new IntegerType(s)); 2594 return result; 2595 } 2596 2597 private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2598 List<Base> result = new ArrayList<Base>(); 2599 result.add(new IntegerType(focus.size())); 2600 return result; 2601 } 2602 2603 private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2604 List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true); 2605 int i1 = Integer.parseInt(n1.get(0).primitiveValue()); 2606 2607 List<Base> result = new ArrayList<Base>(); 2608 for (int i = i1; i < focus.size(); i++) 2609 result.add(focus.get(i)); 2610 return result; 2611 } 2612 2613 private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2614 List<Base> result = new ArrayList<Base>(); 2615 for (int i = 1; i < focus.size(); i++) 2616 result.add(focus.get(i)); 2617 return result; 2618 } 2619 2620 private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2621 List<Base> result = new ArrayList<Base>(); 2622 if (focus.size() > 0) 2623 result.add(focus.get(focus.size()-1)); 2624 return result; 2625 } 2626 2627 private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2628 List<Base> result = new ArrayList<Base>(); 2629 if (focus.size() > 0) 2630 result.add(focus.get(0)); 2631 return result; 2632 } 2633 2634 2635 private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2636 List<Base> result = new ArrayList<Base>(); 2637 List<Base> pc = new ArrayList<Base>(); 2638 for (Base item : focus) { 2639 pc.clear(); 2640 pc.add(item); 2641 if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) 2642 result.add(item); 2643 } 2644 return result; 2645 } 2646 2647 private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2648 List<Base> result = new ArrayList<Base>(); 2649 List<Base> pc = new ArrayList<Base>(); 2650 for (Base item : focus) { 2651 pc.clear(); 2652 pc.add(item); 2653 result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); 2654 } 2655 return result; 2656 } 2657 2658 2659 private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { 2660 List<Base> result = new ArrayList<Base>(); 2661 String s = convertToString(execute(context, focus, exp.getParameters().get(0), true)); 2662 if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) 2663 result.add(focus.get(Integer.parseInt(s))); 2664 return result; 2665 } 2666 2667 private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2668 List<Base> result = new ArrayList<Base>(); 2669 result.add(new BooleanType(ElementUtil.isEmpty(focus))); 2670 return result; 2671 } 2672 2673 private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) { 2674 return makeBoolean(!convertToBoolean(focus)); 2675 } 2676 2677 public class ElementDefinitionMatch { 2678 private ElementDefinition definition; 2679 private String fixedType; 2680 public ElementDefinitionMatch(ElementDefinition definition, String fixedType) { 2681 super(); 2682 this.definition = definition; 2683 this.fixedType = fixedType; 2684 } 2685 public ElementDefinition getDefinition() { 2686 return definition; 2687 } 2688 public String getFixedType() { 2689 return fixedType; 2690 } 2691 2692 } 2693 2694 private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException { 2695 if (Utilities.noString(type)) 2696 throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName"); 2697 if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) 2698 return; 2699 String url = null; 2700 if (type.contains("#")) { 2701 url = type.substring(0, type.indexOf("#")); 2702 } else { 2703 url = type; 2704 } 2705 String tail = ""; 2706 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url); 2707 if (sd == null) 2708 throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong 2709 List<StructureDefinition> sdl = new ArrayList<StructureDefinition>(); 2710 ElementDefinitionMatch m = null; 2711 if (type.contains("#")) 2712 m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false); 2713 if (m != null && hasDataType(m.definition)) { 2714 if (m.fixedType != null) 2715 { 2716 StructureDefinition dt = worker.fetchTypeDefinition(m.fixedType); 2717 if (dt == null) 2718 throw new DefinitionException("unknown data type "+m.fixedType); 2719 sdl.add(dt); 2720 } else 2721 for (TypeRefComponent t : m.definition.getType()) { 2722 StructureDefinition dt = worker.fetchTypeDefinition(t.getCode()); 2723 if (dt == null) 2724 throw new DefinitionException("unknown data type "+t.getCode()); 2725 sdl.add(dt); 2726 } 2727 } else { 2728 sdl.add(sd); 2729 if (type.contains("#")) { 2730 tail = type.substring(type.indexOf("#")+1); 2731 tail = tail.substring(tail.indexOf(".")); 2732 } 2733 } 2734 2735 for (StructureDefinition sdi : sdl) { 2736 String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."; 2737 if (name.equals("**")) { 2738 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2739 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2740 if (ed.getPath().startsWith(path)) 2741 for (TypeRefComponent t : ed.getType()) { 2742 if (t.hasCode() && t.getCodeElement().hasValue()) { 2743 String tn = null; 2744 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2745 tn = sdi.getType()+"#"+ed.getPath(); 2746 else 2747 tn = t.getCode(); 2748 if (t.getCode().equals("Resource")) { 2749 for (String rn : worker.getResourceNames()) { 2750 if (!result.hasType(worker, rn)) { 2751 getChildTypesByName(result.addType(rn), "**", result); 2752 } 2753 } 2754 } else if (!result.hasType(worker, tn)) { 2755 getChildTypesByName(result.addType(tn), "**", result); 2756 } 2757 } 2758 } 2759 } 2760 } else if (name.equals("*")) { 2761 assert(result.getCollectionStatus() == CollectionStatus.UNORDERED); 2762 for (ElementDefinition ed : sdi.getSnapshot().getElement()) { 2763 if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains(".")) 2764 for (TypeRefComponent t : ed.getType()) { 2765 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2766 result.addType(sdi.getType()+"#"+ed.getPath()); 2767 else if (t.getCode().equals("Resource")) 2768 result.addTypes(worker.getResourceNames()); 2769 else 2770 result.addType(t.getCode()); 2771 } 2772 } 2773 } else { 2774 path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name; 2775 2776 ElementDefinitionMatch ed = getElementDefinition(sdi, path, false); 2777 if (ed != null) { 2778 if (!Utilities.noString(ed.getFixedType())) 2779 result.addType(ed.getFixedType()); 2780 else 2781 for (TypeRefComponent t : ed.getDefinition().getType()) { 2782 if (Utilities.noString(t.getCode())) 2783 break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path); 2784 2785 ProfiledType pt = null; 2786 if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) 2787 pt = new ProfiledType(sdi.getUrl()+"#"+path); 2788 else if (t.getCode().equals("Resource")) 2789 result.addTypes(worker.getResourceNames()); 2790 else 2791 pt = new ProfiledType(t.getCode()); 2792 if (pt != null) { 2793 if (t.hasProfile()) 2794 pt.addProfile(t.getProfile()); 2795 if (ed.getDefinition().hasBinding()) 2796 pt.addBinding(ed.getDefinition().getBinding()); 2797 result.addType(pt); 2798 } 2799 } 2800 } 2801 } 2802 } 2803 } 2804 2805 private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException { 2806 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2807 if (ed.getPath().equals(path)) { 2808 if (ed.hasContentReference()) { 2809 return getElementDefinitionById(sd, ed.getContentReference()); 2810 } else 2811 return new ElementDefinitionMatch(ed, null); 2812 } 2813 if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) 2814 return new ElementDefinitionMatch(ed, null); 2815 if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) { 2816 String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3)); 2817 if (primitiveTypes.contains(s)) 2818 return new ElementDefinitionMatch(ed, s); 2819 else 2820 return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)); 2821 } 2822 if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 2823 // now we walk into the type. 2824 if (ed.getType().size() > 1) // if there's more than one type, the test above would fail this 2825 throw new PathEngineException("Internal typing issue...."); 2826 StructureDefinition nsd = worker.fetchTypeDefinition(ed.getType().get(0).getCode()); 2827 if (nsd == null) 2828 throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode()); 2829 return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName); 2830 } 2831 if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) { 2832 ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference()); 2833 return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName); 2834 } 2835 } 2836 return null; 2837 } 2838 2839 private boolean isAbstractType(List<TypeRefComponent> list) { 2840 return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource"); 2841} 2842 2843 2844 private boolean hasType(ElementDefinition ed, String s) { 2845 for (TypeRefComponent t : ed.getType()) 2846 if (s.equalsIgnoreCase(t.getCode())) 2847 return true; 2848 return false; 2849 } 2850 2851 private boolean hasDataType(ElementDefinition ed) { 2852 return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement")); 2853 } 2854 2855 private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) { 2856 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 2857 if (ref.equals("#"+ed.getId())) 2858 return new ElementDefinitionMatch(ed, null); 2859 } 2860 return null; 2861 } 2862 2863 2864 public boolean hasLog() { 2865 return log != null && log.length() > 0; 2866 } 2867 2868 2869 public String takeLog() { 2870 if (!hasLog()) 2871 return ""; 2872 String s = log.toString(); 2873 log = new StringBuilder(); 2874 return s; 2875 } 2876 2877}