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