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