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