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