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