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