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