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