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