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