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