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