001package org.hl7.fhir.r4.utils;
002
003//import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
004import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
005import ca.uhn.fhir.util.ElementUtil;
006
007import org.apache.commons.lang3.NotImplementedException;
008import org.fhir.ucum.Decimal;
009import org.fhir.ucum.Pair;
010import org.fhir.ucum.UcumException;
011import org.hl7.fhir.exceptions.DefinitionException;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.exceptions.PathEngineException;
014import org.hl7.fhir.r4.conformance.ProfileUtilities;
015import org.hl7.fhir.r4.context.IWorkerContext;
016import org.hl7.fhir.r4.model.*;
017import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
018import org.hl7.fhir.r4.model.ExpressionNode.*;
019import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
020import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
021import org.hl7.fhir.r4.model.TypeDetails.ProfiledType;
022import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException;
023import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
024import org.hl7.fhir.utilities.Utilities;
025
026import java.math.BigDecimal;
027import java.util.*;
028
029/**
030 * 
031 * @author Grahame Grieve
032 *
033 */
034public class FHIRPathEngine {
035  private class FHIRConstant extends Base {
036
037    private static final long serialVersionUID = -8933773658248269439L;
038    private String value;
039
040    public FHIRConstant(String value) {
041      this.value = value;
042    }
043
044    @Override
045    public String fhirType() {
046      return "%constant";
047    }
048
049    @Override
050    protected void listChildren(List<Property> result) {
051    }
052
053    @Override
054    public String getIdBase() {
055      return null;
056    }
057
058    @Override
059    public void setIdBase(String value) {
060    }
061
062    public String getValue() {
063      return value;
064    }
065  }
066  
067  private class ClassTypeInfo extends Base {
068    private static final long serialVersionUID = 4909223114071029317L;
069    private Base instance;
070
071    public ClassTypeInfo(Base instance) {
072      super();
073      this.instance = instance;
074    }
075
076    @Override
077    public String fhirType() {
078      return "ClassInfo";
079    }
080
081    @Override
082    protected void listChildren(List<Property> result) {
083    }
084
085    @Override
086    public String getIdBase() {
087      return null;
088    }
089
090    @Override
091    public void setIdBase(String value) {
092    }
093    public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
094      if (name.equals("name")) 
095        return new Base[]{new StringType(getName())};
096      else if (name.equals("namespace")) 
097        return new Base[]{new StringType(getNamespace())};
098      else
099        return super.getProperty(hash, name, checkValid);
100    }
101
102    private String getNamespace() {
103      if ((instance instanceof Resource))
104        return "FHIR";
105      else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions())
106        return "System";
107      else
108        return "FHIR";
109    }
110
111    private String getName() {
112      if ((instance instanceof Resource))
113        return instance.fhirType();
114      else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions())
115        return Utilities.capitalize(instance.fhirType());
116      else
117        return instance.fhirType();
118    }
119  }
120
121  private IWorkerContext worker;
122  private IEvaluationContext hostServices;
123  private StringBuilder log = new StringBuilder();
124  private Set<String> primitiveTypes = new HashSet<String>();
125  private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
126
127  // if the fhir path expressions are allowed to use constants beyond those defined in the specification
128  // the application can implement them by providing a constant resolver 
129  public interface IEvaluationContext {
130    public class FunctionDetails {
131      private String description;
132      private int minParameters;
133      private int maxParameters;
134      public FunctionDetails(String description, int minParameters, int maxParameters) {
135        super();
136        this.description = description;
137        this.minParameters = minParameters;
138        this.maxParameters = maxParameters;
139      }
140      public String getDescription() {
141        return description;
142      }
143      public int getMinParameters() {
144        return minParameters;
145      }
146      public int getMaxParameters() {
147        return maxParameters;
148      }
149
150    }
151
152    /**
153     * A constant reference - e.g. a reference to a name that must be resolved in context.
154     * The % will be removed from the constant name before this is invoked.
155     * 
156     * This will also be called if the host invokes the FluentPath engine with a context of null
157     *  
158     * @param appContext - content passed into the fluent path engine
159     * @param name - name reference to resolve
160     * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
161     */
162    public Base resolveConstant(Object appContext, String name)  throws PathEngineException;
163    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException;
164    
165    /**
166     * when the .log() function is called
167     * 
168     * @param argument
169     * @param focus
170     * @return
171     */
172    public boolean log(String argument, List<Base> focus);
173
174    // extensibility for functions
175    /**
176     * 
177     * @param functionName
178     * @return null if the function is not known
179     */
180    public FunctionDetails resolveFunction(String functionName);
181    
182    /**
183     * Check the function parameters, and throw an error if they are incorrect, or return the type for the function
184     * @param functionName
185     * @param parameters
186     * @return
187     */
188    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException;
189    
190    /**
191     * @param appContext
192     * @param functionName
193     * @param parameters
194     * @return
195     */
196    public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters);
197    
198    /**
199     * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
200     * @param url
201     * @return
202     * @throws FHIRException 
203     */
204    public Base resolveReference(Object appContext, String url) throws FHIRException;
205    
206    public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException;
207  }
208
209
210  /**
211   * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
212   */
213  public FHIRPathEngine(IWorkerContext worker) {
214    super();
215    this.worker = worker;
216    for (StructureDefinition sd : worker.allStructures()) {
217      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL)
218        allTypes.put(sd.getName(), sd);
219      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
220        primitiveTypes.add(sd.getName());
221      }
222    }
223  }
224
225
226  // --- 3 methods to override in children -------------------------------------------------------
227  // if you don't override, it falls through to the using the base reference implementation 
228  // HAPI overrides to these to support extending the base model
229
230  public IEvaluationContext getHostServices() {
231    return hostServices;
232  }
233
234
235  public void setHostServices(IEvaluationContext constantResolver) {
236    this.hostServices = constantResolver;
237  }
238
239
240  /**
241   * Given an item, return all the children that conform to the pattern described in name
242   * 
243   * Possible patterns:
244   *  - a simple name (which may be the base of a name with [] e.g. value[x])
245   *  - a name with a type replacement e.g. valueCodeableConcept
246   *  - * which means all children
247   *  - ** which means all descendants
248   *  
249   * @param item
250   * @param name
251   * @param result
252         * @throws FHIRException 
253   */
254  protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
255        Base[] list = item.listChildrenByName(name, false);
256        if (list != null)
257                for (Base v : list)
258      if (v != null)
259        result.add(v);
260  }
261
262  // --- public API -------------------------------------------------------
263  /**
264   * Parse a path for later use using execute
265   * 
266   * @param path
267   * @return
268   * @throws PathEngineException 
269   * @throws Exception
270   */
271  public ExpressionNode parse(String path) throws FHIRLexerException {
272    FHIRLexer lexer = new FHIRLexer(path);
273    if (lexer.done())
274      throw lexer.error("Path cannot be empty");
275    ExpressionNode result = parseExpression(lexer, true);
276    if (!lexer.done())
277      throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\"");
278    result.check();
279    return result;    
280  }
281
282  /**
283   * Parse a path that is part of some other syntax
284   *  
285   * @return
286   * @throws PathEngineException 
287   * @throws Exception
288   */
289  public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException {
290    ExpressionNode result = parseExpression(lexer, true);
291    result.check();
292    return result;    
293  }
294
295  /**
296   * check that paths referred to in the ExpressionNode are valid
297   * 
298   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
299   * 
300   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
301   * 
302   * @param context - the logical type against which this path is applied
303   * @throws DefinitionException
304   * @throws PathEngineException 
305   * @if the path is not valid
306   */
307  public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
308    // if context is a path that refers to a type, do that conversion now 
309        TypeDetails types; 
310        if (context == null) {
311          types = null; // this is a special case; the first path reference will have to resolve to something in the context
312        } else if (!context.contains(".")) {
313    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context);
314          types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
315        } else {
316          String ctxt = context.substring(0, context.indexOf('.'));
317      if (Utilities.isAbsoluteUrl(resourceType)) {
318        ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt;
319      }
320          StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt);
321          if (sd == null) 
322            throw new PathEngineException("Unknown context "+context);
323          ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
324          if (ed == null) 
325            throw new PathEngineException("Unknown context element "+context);
326          if (ed.fixedType != null) 
327            types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
328          else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 
329            types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context);
330          else {
331            types = new TypeDetails(CollectionStatus.SINGLETON);
332                for (TypeRefComponent t : ed.getDefinition().getType()) 
333                  types.addType(t.getCode());
334          }
335        }
336
337    return executeType(new ExecutionTypeContext(appContext, resourceType, context, types), types, expr, true);
338  }
339
340  public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
341    // if context is a path that refers to a type, do that conversion now 
342    TypeDetails types; 
343    if (!context.contains(".")) {
344      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
345    } else {
346      ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
347      if (ed == null) 
348        throw new PathEngineException("Unknown context element "+context);
349      if (ed.fixedType != null) 
350        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
351      else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 
352        types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context);
353      else {
354        types = new TypeDetails(CollectionStatus.SINGLETON);
355        for (TypeRefComponent t : ed.getDefinition().getType()) 
356          types.addType(t.getCode());
357      }
358    }
359
360    return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), context, types), types, expr, true);
361  }
362
363  public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
364    // if context is a path that refers to a type, do that conversion now 
365    TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
366    return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true);
367  }
368
369  public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
370    return check(appContext, resourceType, context, parse(expr));
371  }
372
373  private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
374    String dateLeftString = theL.primitiveValue();
375    DateTimeType dateLeft = new DateTimeType(dateLeftString);
376
377    String dateRightString = theR.primitiveValue();
378    DateTimeType dateRight = new DateTimeType(dateRightString);
379
380    if (theEquivalenceTest) {
381      TemporalPrecisionEnum lowestPrecision = dateLeft.getPrecision().ordinal() < dateRight.getPrecision().ordinal() ? dateLeft.getPrecision() : dateRight.getPrecision();
382      dateLeft.setPrecision(lowestPrecision);
383      dateRight.setPrecision(lowestPrecision);
384    }
385
386    if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
387      dateLeft.setTimeZoneZulu(true);
388    }
389    if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
390      dateRight.setTimeZoneZulu(true);
391    }
392
393    dateLeftString = dateLeft.getValueAsString();
394    dateRightString = dateRight.getValueAsString();
395
396    return dateLeftString.compareTo(dateRightString);
397  }
398
399  /**
400   * evaluate a path and return the matching elements
401   * 
402   * @param base - the object against which the path is being evaluated
403   * @param ExpressionNode - the parsed ExpressionNode statement to use
404   * @return
405   * @throws FHIRException 
406   * @
407   */
408        public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException {
409    List<Base> list = new ArrayList<Base>();
410    if (base != null)
411      list.add(base);
412    log = new StringBuilder();
413    return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true);
414  }
415
416  /**
417   * evaluate a path and return the matching elements
418   * 
419   * @param base - the object against which the path is being evaluated
420   * @param path - the FHIR Path statement to use
421   * @return
422         * @throws FHIRException 
423   * @
424   */
425        public List<Base> evaluate(Base base, String path) throws FHIRException {
426    ExpressionNode exp = parse(path);
427    List<Base> list = new ArrayList<Base>();
428    if (base != null)
429      list.add(base);
430    log = new StringBuilder();
431    return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true);
432  }
433
434  /**
435   * evaluate a path and return the matching elements
436   * 
437   * @param base - the object against which the path is being evaluated
438   * @param ExpressionNode - the parsed ExpressionNode statement to use
439   * @return
440         * @throws FHIRException 
441   * @
442   */
443        public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
444    List<Base> list = new ArrayList<Base>();
445    if (base != null)
446      list.add(base);
447    log = new StringBuilder();
448    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
449  }
450
451  /**
452   * evaluate a path and return the matching elements
453   * 
454   * @param base - the object against which the path is being evaluated
455   * @param ExpressionNode - the parsed ExpressionNode statement to use
456   * @return
457   * @throws FHIRException 
458   * @
459   */
460  public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
461    List<Base> list = new ArrayList<Base>();
462    if (base != null)
463      list.add(base);
464    log = new StringBuilder();
465    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
466  }
467
468  /**
469   * evaluate a path and return the matching elements
470   * 
471   * @param base - the object against which the path is being evaluated
472   * @param path - the FHIR Path statement to use
473   * @return
474         * @throws FHIRException 
475   * @
476   */
477        public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException {
478    ExpressionNode exp = parse(path);
479    List<Base> list = new ArrayList<Base>();
480    if (base != null)
481      list.add(base);
482    log = new StringBuilder();
483    return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true);
484  }
485
486  /**
487   * evaluate a path and return true or false (e.g. for an invariant)
488   * 
489   * @param base - the object against which the path is being evaluated
490   * @param path - the FHIR Path statement to use
491   * @return
492         * @throws FHIRException 
493   * @
494   */
495        public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException {
496    return convertToBoolean(evaluate(null, resource, base, path));
497  }
498
499  /**
500   * evaluate a path and return true or false (e.g. for an invariant)
501   * 
502   * @param base - the object against which the path is being evaluated
503   * @return
504   * @throws FHIRException 
505   * @
506   */
507  public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException {
508    return convertToBoolean(evaluate(null, resource, base, node));
509  }
510
511  /**
512   * evaluate a path and return true or false (e.g. for an invariant)
513   * 
514   * @param appInfo - application context
515   * @param base - the object against which the path is being evaluated
516   * @return
517   * @throws FHIRException 
518   * @
519   */
520  public boolean evaluateToBoolean(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException {
521    return convertToBoolean(evaluate(appInfo, resource, base, node));
522  }
523
524  /**
525   * evaluate a path and return true or false (e.g. for an invariant)
526   * 
527   * @param base - the object against which the path is being evaluated
528   * @return
529   * @throws FHIRException 
530   * @
531   */
532  public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException {
533    return convertToBoolean(evaluate(null, resource, base, node));
534  }
535
536  /**
537   * evaluate a path and a string containing the outcome (for display)
538   * 
539   * @param base - the object against which the path is being evaluated
540   * @param path - the FHIR Path statement to use
541   * @return
542         * @throws FHIRException 
543   * @
544   */
545  public String evaluateToString(Base base, String path) throws FHIRException {
546    return convertToString(evaluate(base, path));
547  }
548
549  public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException {
550    return convertToString(evaluate(appInfo, resource, base, node));
551  }
552
553  /**
554   * worker routine for converting a set of objects to a string representation
555   * 
556   * @param items - result from @evaluate
557   * @return
558   */
559  public String convertToString(List<Base> items) {
560    StringBuilder b = new StringBuilder();
561    boolean first = true;
562    for (Base item : items) {
563      if (first) 
564        first = false;
565      else
566        b.append(',');
567
568      b.append(convertToString(item));
569    }
570    return b.toString();
571  }
572
573  private String convertToString(Base item) {
574    if (item.isPrimitive())
575      return item.primitiveValue();
576    else if (item instanceof Quantity) {
577      Quantity q = (Quantity) item;
578      if (q.getSystem().equals("http://unitsofmeasure.org")) {
579        String u = "'"+q.getCode()+"'";
580        boolean plural = !q.getValue().toPlainString().equals("1");
581        if ("a".equals(q.getCode()))
582          u = plural ? "years" : "year";
583        else if ("mo".equals(q.getCode()))
584          u = plural ? "months" : "month";
585        else if ("wk".equals(q.getCode()))
586          u = plural ? "weeks" : "week";
587        else if ("d".equals(q.getCode()))
588          u = plural ? "days" : "day";
589        else if ("h".equals(q.getCode()))
590          u = plural ? "hours" : "hour";
591        else if ("min".equals(q.getCode()))
592          u = plural ? "minutes" : "minute";
593        else if ("s".equals(q.getCode()))
594          u = plural ? "seconds" : "seconds";
595        else if ("ms".equals(q.getCode()))
596          u = plural ? "milliseconds" : "milliseconds";
597        return q.getValue().toPlainString()+" "+u;
598      }
599      else
600        return item.toString();
601    } else
602      return item.toString();
603  }
604
605  /**
606   * worker routine for converting a set of objects to a boolean representation (for invariants)
607   * 
608   * @param items - result from @evaluate
609   * @return
610   */
611  public boolean convertToBoolean(List<Base> items) {
612    if (items == null)
613      return false;
614    else if (items.size() == 1 && items.get(0) instanceof BooleanType)
615      return ((BooleanType) items.get(0)).getValue();
616    else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model
617      return Boolean.valueOf(items.get(0).primitiveValue());
618    else 
619      return items.size() > 0;
620  }
621
622
623  private void log(String name, List<Base> contents) {
624    if (hostServices == null || !hostServices.log(name, contents)) {
625      if (log.length() > 0)
626        log.append("; ");
627      log.append(name);
628      log.append(": ");
629      boolean first = true;
630      for (Base b : contents) {
631        if (first)
632          first = false;
633        else
634          log.append(",");
635        log.append(convertToString(b));
636      }
637    }
638  }
639
640  public String forLog() {
641    if (log.length() > 0)
642      return " ("+log.toString()+")";
643    else
644      return "";
645  }
646
647  private class ExecutionContext {
648    private Object appInfo;
649    private Base resource;
650    private Base context;
651    private Base thisItem;
652    private List<Base> total;
653    private Map<String, Base> aliases;
654    
655    public ExecutionContext(Object appInfo, Base resource, Base context, Map<String, Base> aliases, Base thisItem) {
656      this.appInfo = appInfo;
657      this.context = context;
658      this.resource = resource; 
659      this.aliases = aliases;
660      this.thisItem = thisItem;
661    }
662    public Base getResource() {
663      return resource;
664    }
665    public Base getThisItem() {
666      return thisItem;
667    }
668    public List<Base> getTotal() {
669      return total;
670    }
671    public void addAlias(String name, List<Base> focus) throws FHIRException {
672      if (aliases == null)
673        aliases = new HashMap<String, Base>();
674      else
675        aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 
676      if (focus.size() > 1)
677        throw new FHIRException("Attempt to alias a collection, not a singleton");
678      aliases.put(name, focus.size() == 0 ? null : focus.get(0));      
679    }
680    public Base getAlias(String name) {
681      return aliases == null ? null : aliases.get(name);
682    }
683  }
684
685  private class ExecutionTypeContext {
686    private Object appInfo; 
687    private String resource;
688    private String context;
689    private TypeDetails thisItem;
690    private TypeDetails total;
691
692
693    public ExecutionTypeContext(Object appInfo, String resource, String context, TypeDetails thisItem) {
694      super();
695      this.appInfo = appInfo;
696      this.resource = resource;
697      this.context = context;
698      this.thisItem = thisItem;
699      
700    }
701    public String getResource() {
702      return resource;
703    }
704    public TypeDetails getThisItem() {
705      return thisItem;
706    }
707
708    
709  }
710
711  private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
712    ExpressionNode result = new ExpressionNode(lexer.nextId());
713    SourceLocation c = lexer.getCurrentStartLocation();
714    result.setStart(lexer.getCurrentLocation());
715    // special:
716    if (lexer.getCurrent().equals("-")) {
717      lexer.take();
718      lexer.setCurrent("-"+lexer.getCurrent());
719    }
720    if (lexer.getCurrent().equals("+")) {
721      lexer.take();
722      lexer.setCurrent("+"+lexer.getCurrent());
723    }
724    if (lexer.isConstant(false)) {
725      boolean isString = lexer.isStringConstant();
726      result.setConstant(processConstant(lexer));
727      result.setKind(Kind.Constant);
728      if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) {
729        // it's a quantity
730        String ucum = null;
731        if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) {
732          String s = lexer.take();
733          if (s.equals("year") || s.equals("years"))
734            ucum = "a";
735          else if (s.equals("month") || s.equals("months"))
736            ucum = "mo";
737          else if (s.equals("week") || s.equals("weeks"))
738            ucum = "wk";
739          else if (s.equals("day") || s.equals("days"))
740            ucum = "d";
741          else if (s.equals("hour") || s.equals("hours"))
742            ucum = "h";
743          else if (s.equals("minute") || s.equals("minutes"))
744            ucum = "min";
745          else if (s.equals("second") || s.equals("seconds"))
746            ucum = "s";
747          else // (s.equals("millisecond") || s.equals("milliseconds"))
748            ucum = "ms";
749        } else 
750          ucum = lexer.readConstant("units");
751        result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum));
752      }
753      result.setEnd(lexer.getCurrentLocation());
754    } else if ("(".equals(lexer.getCurrent())) {
755      lexer.next();
756      result.setKind(Kind.Group);
757      result.setGroup(parseExpression(lexer, true));
758      if (!")".equals(lexer.getCurrent())) 
759        throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\"");
760      result.setEnd(lexer.getCurrentLocation());
761      lexer.next();
762    } else {
763      if (!lexer.isToken() && !lexer.getCurrent().startsWith("\"")) 
764        throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name");
765      if (lexer.getCurrent().startsWith("\""))
766        result.setName(lexer.readConstant("Path Name"));
767      else
768        result.setName(lexer.take());
769      result.setEnd(lexer.getCurrentLocation());
770      if (!result.checkName())
771        throw lexer.error("Found "+result.getName()+" expecting a valid token name");
772      if ("(".equals(lexer.getCurrent())) {
773        Function f = Function.fromCode(result.getName());
774        FunctionDetails details = null;
775        if (f == null) {
776          if (hostServices != null)
777            details = hostServices.resolveFunction(result.getName());
778          if (details == null)
779            throw lexer.error("The name "+result.getName()+" is not a valid function name");
780          f = Function.Custom;
781        }
782        result.setKind(Kind.Function);
783        result.setFunction(f);
784        lexer.next();
785        while (!")".equals(lexer.getCurrent())) { 
786          result.getParameters().add(parseExpression(lexer, true));
787          if (",".equals(lexer.getCurrent()))
788            lexer.next();
789          else if (!")".equals(lexer.getCurrent()))
790            throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected");
791        }
792        result.setEnd(lexer.getCurrentLocation());
793        lexer.next();
794        checkParameters(lexer, c, result, details);
795      } else
796        result.setKind(Kind.Name);
797    }
798    ExpressionNode focus = result;
799    if ("[".equals(lexer.getCurrent())) {
800      lexer.next();
801      ExpressionNode item = new ExpressionNode(lexer.nextId());
802      item.setKind(Kind.Function);
803      item.setFunction(ExpressionNode.Function.Item);
804      item.getParameters().add(parseExpression(lexer, true));
805      if (!lexer.getCurrent().equals("]"))
806        throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected");
807      lexer.next();
808      result.setInner(item);
809      focus = item;
810    }
811    if (".".equals(lexer.getCurrent())) {
812      lexer.next();
813      focus.setInner(parseExpression(lexer, false));
814    }
815    result.setProximal(proximal);
816    if (proximal) {
817      while (lexer.isOp()) {
818        focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent()));
819        focus.setOpStart(lexer.getCurrentStartLocation());
820        focus.setOpEnd(lexer.getCurrentLocation());
821        lexer.next();
822        focus.setOpNext(parseExpression(lexer, false));
823        focus = focus.getOpNext();
824      }
825      result = organisePrecedence(lexer, result);
826    }
827    return result;
828  }
829
830  private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) {
831    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 
832    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 
833    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 
834    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThen, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual));
835    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is));
836    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
837    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
838    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
839    // last: implies
840    return node;
841  }
842
843  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
844    //    work : boolean;
845    //    focus, node, group : ExpressionNode;
846
847    assert(start.isProximal());
848
849    // is there anything to do?
850    boolean work = false;
851    ExpressionNode focus = start.getOpNext();
852    if (ops.contains(start.getOperation())) {
853      while (focus != null && focus.getOperation() != null) {
854        work = work || !ops.contains(focus.getOperation());
855        focus = focus.getOpNext();
856      }
857    } else {
858      while (focus != null && focus.getOperation() != null) {
859        work = work || ops.contains(focus.getOperation());
860        focus = focus.getOpNext();
861      }
862    }  
863    if (!work)
864      return start;
865
866    // entry point: tricky
867    ExpressionNode group;
868    if (ops.contains(start.getOperation())) {
869      group = newGroup(lexer, start);
870      group.setProximal(true);
871      focus = start;
872      start = group;
873    } else {
874      ExpressionNode node = start;
875
876      focus = node.getOpNext();
877      while (!ops.contains(focus.getOperation())) {
878        node = focus;
879        focus = focus.getOpNext();
880      }
881      group = newGroup(lexer, focus);
882      node.setOpNext(group);
883    }
884
885    // now, at this point:
886    //   group is the group we are adding to, it already has a .group property filled out.
887    //   focus points at the group.group
888    do {
889      // run until we find the end of the sequence
890      while (ops.contains(focus.getOperation()))
891        focus = focus.getOpNext();
892      if (focus.getOperation() != null) {
893        group.setOperation(focus.getOperation());
894        group.setOpNext(focus.getOpNext());
895        focus.setOperation(null);
896        focus.setOpNext(null);
897        // now look for another sequence, and start it
898        ExpressionNode node = group;
899        focus = group.getOpNext();
900        if (focus != null) { 
901          while (focus != null && !ops.contains(focus.getOperation())) {
902            node = focus;
903            focus = focus.getOpNext();
904          }
905          if (focus != null) { // && (focus.Operation in Ops) - must be true 
906            group = newGroup(lexer, focus);
907            node.setOpNext(group);
908          }
909        }
910      }
911    }
912    while (focus != null && focus.getOperation() != null);
913    return start;
914  }
915
916
917  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
918    ExpressionNode result = new ExpressionNode(lexer.nextId());
919    result.setKind(Kind.Group);
920    result.setGroup(next);
921    result.getGroup().setProximal(true);
922    return result;
923  }
924
925  private Base processConstant(FHIRLexer lexer) throws FHIRLexerException {
926    if (lexer.isStringConstant()) {
927      return new StringType(processConstantString(lexer.take(), lexer)).noExtensions();
928    } else if (Utilities.isInteger(lexer.getCurrent())) {
929      return new IntegerType(lexer.take()).noExtensions();
930    } else if (Utilities.isDecimal(lexer.getCurrent())) {
931      return new DecimalType(lexer.take()).noExtensions();
932    } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) {
933      return new BooleanType(lexer.take()).noExtensions();
934    } else if (lexer.getCurrent().equals("{}")) {
935      lexer.take();
936      return null;
937    } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) {
938      return new FHIRConstant(lexer.take());
939    } else
940      throw lexer.error("Invalid Constant "+lexer.getCurrent());
941  }
942
943  //  procedure CheckParamCount(c : integer);
944  //  begin
945  //    if exp.Parameters.Count <> c then
946  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
947  //  end;
948
949  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
950    if (exp.getParameters().size() != count)
951      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString());
952    return true;
953  }
954
955  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
956    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax)
957      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString());
958    return true;
959  }
960
961  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
962    switch (exp.getFunction()) {
963    case Empty: return checkParamCount(lexer, location, exp, 0);
964    case Not: return checkParamCount(lexer, location, exp, 0);
965    case Exists: return checkParamCount(lexer, location, exp, 0);
966    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
967    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
968    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
969    case Distinct: return checkParamCount(lexer, location, exp, 0);
970    case Count: return checkParamCount(lexer, location, exp, 0);
971    case Where: return checkParamCount(lexer, location, exp, 1);
972    case Select: return checkParamCount(lexer, location, exp, 1);
973    case All: return checkParamCount(lexer, location, exp, 0, 1);
974    case Repeat: return checkParamCount(lexer, location, exp, 1);
975    case Aggregate: return checkParamCount(lexer, location, exp, 1, 2);
976    case Item: return checkParamCount(lexer, location, exp, 1);
977    case As: return checkParamCount(lexer, location, exp, 1);
978    case OfType: return checkParamCount(lexer, location, exp, 1);
979    case Type: return checkParamCount(lexer, location, exp, 0);
980    case Is: return checkParamCount(lexer, location, exp, 1);
981    case Single: return checkParamCount(lexer, location, exp, 0);
982    case First: return checkParamCount(lexer, location, exp, 0);
983    case Last: return checkParamCount(lexer, location, exp, 0);
984    case Tail: return checkParamCount(lexer, location, exp, 0);
985    case Skip: return checkParamCount(lexer, location, exp, 1);
986    case Take: return checkParamCount(lexer, location, exp, 1);
987    case Union: return checkParamCount(lexer, location, exp, 1);
988    case Combine: return checkParamCount(lexer, location, exp, 1);
989    case Intersect: return checkParamCount(lexer, location, exp, 1);
990    case Exclude: return checkParamCount(lexer, location, exp, 1);
991    case Iif: return checkParamCount(lexer, location, exp, 2,3);
992    case Lower: return checkParamCount(lexer, location, exp, 0);
993    case Upper: return checkParamCount(lexer, location, exp, 0);
994    case ToChars: return checkParamCount(lexer, location, exp, 0);
995    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
996    case StartsWith: return checkParamCount(lexer, location, exp, 1);
997    case EndsWith: return checkParamCount(lexer, location, exp, 1);
998    case Matches: return checkParamCount(lexer, location, exp, 1);
999    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
1000    case Contains: return checkParamCount(lexer, location, exp, 1);
1001    case Replace: return checkParamCount(lexer, location, exp, 2);
1002    case Length: return checkParamCount(lexer, location, exp, 0);
1003    case Children: return checkParamCount(lexer, location, exp, 0);
1004    case Descendants: return checkParamCount(lexer, location, exp, 0);
1005    case MemberOf: return checkParamCount(lexer, location, exp, 1);
1006    case Trace: return checkParamCount(lexer, location, exp, 1, 2);
1007    case Today: return checkParamCount(lexer, location, exp, 0);
1008    case Now: return checkParamCount(lexer, location, exp, 0);
1009    case Resolve: return checkParamCount(lexer, location, exp, 0);
1010    case Extension: return checkParamCount(lexer, location, exp, 1);
1011    case HasValue: return checkParamCount(lexer, location, exp, 0);
1012    case Alias: return checkParamCount(lexer, location, exp, 1);
1013    case AliasAs: return checkParamCount(lexer, location, exp, 1);
1014    case HtmlChecks: return checkParamCount(lexer, location, exp, 0);
1015    case ToInteger: return checkParamCount(lexer, location, exp, 0);
1016    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
1017    case ToString: return checkParamCount(lexer, location, exp, 0);
1018    case ToQuantity: return checkParamCount(lexer, location, exp, 0);
1019    case ToBoolean: return checkParamCount(lexer, location, exp, 0);
1020    case ToDateTime: return checkParamCount(lexer, location, exp, 0);
1021    case ToTime: return checkParamCount(lexer, location, exp, 0);
1022    case IsInteger: return checkParamCount(lexer, location, exp, 0);
1023    case IsDecimal: return checkParamCount(lexer, location, exp, 0);
1024    case IsString: return checkParamCount(lexer, location, exp, 0);
1025    case IsQuantity: return checkParamCount(lexer, location, exp, 0);
1026    case IsBoolean: return checkParamCount(lexer, location, exp, 0);
1027    case IsDateTime: return checkParamCount(lexer, location, exp, 0);
1028    case IsTime: return checkParamCount(lexer, location, exp, 0);
1029    case ConformsTo: return checkParamCount(lexer, location, exp, 1);
1030    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
1031    }
1032    return false;
1033  }
1034
1035        private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
1036//    System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
1037    List<Base> work = new ArrayList<Base>();
1038    switch (exp.getKind()) {
1039    case Name:
1040      if (atEntry && exp.getName().equals("$this"))
1041        work.add(context.getThisItem());
1042      else if (atEntry && exp.getName().equals("$total"))
1043        work.addAll(context.getTotal());
1044      else
1045        for (Base item : focus) {
1046          List<Base> outcome = execute(context, item, exp, atEntry);
1047          for (Base base : outcome)
1048            if (base != null)
1049              work.add(base);
1050        }                       
1051      break;
1052    case Function:
1053      List<Base> work2 = evaluateFunction(context, focus, exp);
1054      work.addAll(work2);
1055      break;
1056    case Constant:
1057      Base b = resolveConstant(context, exp.getConstant());
1058      if (b != null)
1059        work.add(b);
1060      break;
1061    case Group:
1062      work2 = execute(context, focus, exp.getGroup(), atEntry);
1063      work.addAll(work2);
1064    }
1065
1066    if (exp.getInner() != null)
1067      work = execute(context, work, exp.getInner(), false);
1068
1069    if (exp.isProximal() && exp.getOperation() != null) {
1070      ExpressionNode next = exp.getOpNext();
1071      ExpressionNode last = exp;
1072      while (next != null) {
1073        List<Base> work2 = preOperate(work, last.getOperation());
1074        if (work2 != null)
1075          work = work2;
1076        else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1077          work2 = executeTypeName(context, focus, next, false);
1078          work = operate(work, last.getOperation(), work2);
1079        } else {
1080          work2 = execute(context, focus, next, true);
1081          work = operate(work, last.getOperation(), work2);
1082//          System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString());
1083        }
1084        last = next;
1085        next = next.getOpNext();
1086      }
1087    }
1088//    System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString());
1089    return work;
1090  }
1091
1092  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
1093    List<Base> result = new ArrayList<Base>();
1094    result.add(new StringType(next.getName()));
1095    return result;
1096  }
1097
1098
1099  private List<Base> preOperate(List<Base> left, Operation operation) {
1100    switch (operation) {
1101    case And:
1102      return isBoolean(left, false) ? makeBoolean(false) : null;
1103    case Or:
1104      return isBoolean(left, true) ? makeBoolean(true) : null;
1105    case Implies:
1106      return convertToBoolean(left) ? null : makeBoolean(true);
1107    default: 
1108      return null;
1109    }
1110  }
1111
1112  private List<Base> makeBoolean(boolean b) {
1113    List<Base> res = new ArrayList<Base>();
1114    res.add(new BooleanType(b).noExtensions());
1115    return res;
1116  }
1117
1118  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1119    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
1120  }
1121
1122  private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1123    TypeDetails result = new TypeDetails(null);
1124    switch (exp.getKind()) {
1125    case Name:
1126      if (atEntry && exp.getName().equals("$this"))
1127        result.update(context.getThisItem());
1128      else if (atEntry && exp.getName().equals("$total"))
1129        result.update(anything(CollectionStatus.UNORDERED));
1130      else if (atEntry && focus == null)
1131        result.update(executeContextType(context, exp.getName()));
1132      else {
1133        for (String s : focus.getTypes()) {
1134          result.update(executeType(s, exp, atEntry));
1135        }
1136        if (result.hasNoTypes()) 
1137          throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe());
1138      }
1139      break;
1140    case Function:
1141      result.update(evaluateFunctionType(context, focus, exp));
1142      break;
1143    case Constant:
1144      result.update(resolveConstantType(context, exp.getConstant()));
1145      break;
1146    case Group:
1147      result.update(executeType(context, focus, exp.getGroup(), atEntry));
1148    }
1149    exp.setTypes(result);
1150
1151    if (exp.getInner() != null) {
1152      result = executeType(context, result, exp.getInner(), false);
1153    }
1154
1155    if (exp.isProximal() && exp.getOperation() != null) {
1156      ExpressionNode next = exp.getOpNext();
1157      ExpressionNode last = exp;
1158      while (next != null) {
1159        TypeDetails work;
1160        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As)
1161          work = executeTypeName(context, focus, next, atEntry);
1162        else
1163          work = executeType(context, focus, next, atEntry);
1164        result = operateTypes(result, last.getOperation(), work);
1165        last = next;
1166        next = next.getOpNext();
1167      }
1168      exp.setOpTypes(result);
1169    }
1170    return result;
1171  }
1172
1173  private Base resolveConstant(ExecutionContext context, Base constant) throws PathEngineException {
1174    if (!(constant instanceof FHIRConstant))
1175      return constant;
1176    FHIRConstant c = (FHIRConstant) constant;
1177    if (c.getValue().startsWith("%")) {
1178      return resolveConstant(context, c.getValue());
1179    } else if (c.getValue().startsWith("@")) {
1180      return processDateConstant(context.appInfo, c.getValue().substring(1));
1181    } else 
1182      throw new PathEngineException("Invaild FHIR Constant "+c.getValue());
1183  }
1184
1185  private Base processDateConstant(Object appInfo, String value) throws PathEngineException {
1186    if (value.startsWith("T"))
1187      return new TimeType(value.substring(1)).noExtensions();
1188    String v = value;
1189    if (v.length() > 10) {
1190      int i = v.substring(10).indexOf("-");
1191      if (i == -1)
1192        i = v.substring(10).indexOf("+");
1193      if (i == -1)
1194        i = v.substring(10).indexOf("Z");
1195      v = i == -1 ? value : v.substring(0,  10+i);
1196    }
1197    if (v.length() > 10)
1198      return new DateTimeType(value).noExtensions();
1199    else 
1200      return new DateType(value).noExtensions();
1201  }
1202
1203
1204  private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException {
1205    if (s.equals("%sct"))
1206      return new StringType("http://snomed.info/sct").noExtensions();
1207    else if (s.equals("%loinc"))
1208      return new StringType("http://loinc.org").noExtensions();
1209    else if (s.equals("%ucum"))
1210      return new StringType("http://unitsofmeasure.org").noExtensions();
1211    else if (s.equals("%resource")) {
1212      if (context.resource == null)
1213        throw new PathEngineException("Cannot use %resource in this context");
1214      return context.resource;
1215    } else if (s.equals("%context")) {
1216      return context.context;
1217    } else if (s.equals("%us-zip"))
1218      return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions();
1219    else if (s.startsWith("%\"vs-"))
1220      return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions();
1221    else if (s.startsWith("%\"cs-"))
1222      return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions();
1223    else if (s.startsWith("%\"ext-"))
1224      return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions();
1225    else if (hostServices == null)
1226      throw new PathEngineException("Unknown fixed constant '"+s+"'");
1227    else
1228      return hostServices.resolveConstant(context.appInfo, s.substring(1));
1229  }
1230
1231
1232  private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException {
1233    StringBuilder b = new StringBuilder();
1234    int i = 1;
1235    while (i < s.length()-1) {
1236      char ch = s.charAt(i);
1237      if (ch == '\\') {
1238        i++;
1239        switch (s.charAt(i)) {
1240        case 't': 
1241          b.append('\t');
1242          break;
1243        case 'r':
1244          b.append('\r');
1245          break;
1246        case 'n': 
1247          b.append('\n');
1248          break;
1249        case 'f': 
1250          b.append('\f');
1251          break;
1252        case '\'':
1253          b.append('\'');
1254          break;
1255        case '"':
1256          b.append('"');
1257          break;
1258        case '\\': 
1259          b.append('\\');
1260          break;
1261        case '/': 
1262          b.append('/');
1263          break;
1264        case 'u':
1265          i++;
1266          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1267          b.append((char) uc);
1268          i = i + 3;
1269          break;
1270        default:
1271          throw lexer.error("Unknown character escape \\"+s.charAt(i));
1272        }
1273        i++;
1274      } else {
1275        b.append(ch);
1276        i++;
1277      }
1278    }
1279    return b.toString();
1280  }
1281
1282
1283  private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException {
1284    switch (operation) {
1285    case Equals: return opEquals(left, right);
1286    case Equivalent: return opEquivalent(left, right);
1287    case NotEquals: return opNotEquals(left, right);
1288    case NotEquivalent: return opNotEquivalent(left, right);
1289    case LessThen: return opLessThen(left, right);
1290    case Greater: return opGreater(left, right);
1291    case LessOrEqual: return opLessOrEqual(left, right);
1292    case GreaterOrEqual: return opGreaterOrEqual(left, right);
1293    case Union: return opUnion(left, right);
1294    case In: return opIn(left, right);
1295    case MemberOf: return opMemberOf(left, right);
1296    case Contains: return opContains(left, right);
1297    case Or:  return opOr(left, right);
1298    case And:  return opAnd(left, right);
1299    case Xor: return opXor(left, right);
1300    case Implies: return opImplies(left, right);
1301    case Plus: return opPlus(left, right);
1302    case Times: return opTimes(left, right);
1303    case Minus: return opMinus(left, right);
1304    case Concatenate: return opConcatenate(left, right);
1305    case DivideBy: return opDivideBy(left, right);
1306    case Div: return opDiv(left, right);
1307    case Mod: return opMod(left, right);
1308    case Is: return opIs(left, right);
1309    case As: return opAs(left, right);
1310    default: 
1311      throw new Error("Not Done Yet: "+operation.toCode());
1312    }
1313  }
1314
1315  private List<Base> opAs(List<Base> left, List<Base> right) {
1316    List<Base> result = new ArrayList<Base>();
1317    if (right.size() != 1)
1318      return result;
1319    else {
1320      String tn = convertToString(right);
1321      for (Base nextLeft : left) {
1322        if (tn.equals(nextLeft.fhirType()))
1323          result.add(nextLeft);
1324      }
1325    }
1326    return result;
1327  }
1328
1329
1330  private List<Base> opIs(List<Base> left, List<Base> right) {
1331    List<Base> result = new ArrayList<Base>();
1332    if (left.size() != 1 || right.size() != 1) 
1333      result.add(new BooleanType(false).noExtensions());
1334    else {
1335      String tn = convertToString(right);
1336      if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element)
1337        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1338      else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions())
1339        result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn)).noExtensions());
1340      else
1341        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1342    }
1343    return result;
1344  }
1345
1346
1347  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) {
1348    switch (operation) {
1349    case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1350    case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1351    case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1352    case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1353    case LessThen: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1354    case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1355    case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1356    case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1357    case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1358    case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
1359    case Union: return left.union(right);
1360    case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1361    case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1362    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1363    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1364    case Times: 
1365      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
1366      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1367        result.addType(TypeDetails.FP_Integer);
1368      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1369        result.addType(TypeDetails.FP_Decimal);
1370      return result;
1371    case DivideBy: 
1372      result = new TypeDetails(CollectionStatus.SINGLETON);
1373      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1374        result.addType(TypeDetails.FP_Decimal);
1375      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1376        result.addType(TypeDetails.FP_Decimal);
1377      return result;
1378    case Concatenate:
1379      result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
1380      return result;
1381    case Plus:
1382      result = new TypeDetails(CollectionStatus.SINGLETON);
1383      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1384        result.addType(TypeDetails.FP_Integer);
1385      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1386        result.addType(TypeDetails.FP_Decimal);
1387      else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri"))
1388        result.addType(TypeDetails.FP_String);
1389      return result;
1390    case Minus:
1391      result = new TypeDetails(CollectionStatus.SINGLETON);
1392      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1393        result.addType(TypeDetails.FP_Integer);
1394      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1395        result.addType(TypeDetails.FP_Decimal);
1396      return result;
1397    case Div: 
1398    case Mod: 
1399      result = new TypeDetails(CollectionStatus.SINGLETON);
1400      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1401        result.addType(TypeDetails.FP_Integer);
1402      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1403        result.addType(TypeDetails.FP_Decimal);
1404      return result;
1405    case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1406    case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1407    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1408    default: 
1409      return null;
1410    }
1411  }
1412
1413
1414  private List<Base> opEquals(List<Base> left, List<Base> right) {
1415    if (left.size() != right.size())
1416      return makeBoolean(false);
1417
1418    boolean res = true;
1419    for (int i = 0; i < left.size(); i++) {
1420      if (!doEquals(left.get(i), right.get(i))) { 
1421        res = false;
1422        break;
1423      }
1424    }
1425    return makeBoolean(res);
1426  }
1427
1428  private List<Base> opNotEquals(List<Base> left, List<Base> right) {
1429    if (left.size() != right.size())
1430      return makeBoolean(true);
1431
1432    boolean res = true;
1433    for (int i = 0; i < left.size(); i++) {
1434      if (!doEquals(left.get(i), right.get(i))) { 
1435        res = false;
1436        break;
1437      }
1438    }
1439    return makeBoolean(!res);
1440  }
1441
1442  private boolean doEquals(Base left, Base right) {
1443    if (left instanceof Quantity && right instanceof Quantity)
1444      return qtyEqual((Quantity) left, (Quantity) right);
1445    else if (left.isPrimitive() && right.isPrimitive())
1446                        return Base.equals(left.primitiveValue(), right.primitiveValue());
1447    else
1448      return Base.compareDeep(left, right, false);
1449  }
1450
1451
1452  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
1453    if (left instanceof Quantity && right instanceof Quantity)
1454      return qtyEquivalent((Quantity) left, (Quantity) right);
1455    if (left.hasType("integer") && right.hasType("integer"))
1456      return doEquals(left, right);
1457    if (left.hasType("boolean") && right.hasType("boolean"))
1458      return doEquals(left, right);
1459    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt"))
1460      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1461    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant"))
1462      return compareDateTimeElements(left, right, true) == 0;
1463    if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri"))
1464      return Utilities.equivalent(convertToString(left), convertToString(right));
1465
1466    throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()));
1467  }
1468
1469  private boolean qtyEqual(Quantity left, Quantity right) {
1470    if (worker.getUcumService() != null) {
1471      DecimalType dl = qtyToCanonical(left);
1472      DecimalType dr = qtyToCanonical(right);
1473      if (dl != null && dr != null) 
1474        return doEquals(dl,  dr);
1475    }
1476    return left.equals(right);
1477  }
1478
1479  private DecimalType qtyToCanonical(Quantity q) {
1480    if (!"http://unitsofmeasure.org".equals(q.getSystem()))
1481      return null;
1482    try {
1483      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
1484      Pair c = worker.getUcumService().getCanonicalForm(p);
1485      return new DecimalType(c.getValue().asDecimal());
1486    } catch (UcumException e) {
1487      return null;
1488    }
1489  }
1490
1491  private Base pairToQty(Pair p) {
1492    return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions();
1493  }
1494
1495
1496  private Pair qtyToPair(Quantity q) {
1497    if (!"http://unitsofmeasure.org".equals(q.getSystem()))
1498      return null;
1499    try {
1500      return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
1501    } catch (UcumException e) {
1502      return null;
1503    }
1504  }
1505
1506
1507  private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
1508    if (worker.getUcumService() != null) {
1509      DecimalType dl = qtyToCanonical(left);
1510      DecimalType dr = qtyToCanonical(right);
1511      if (dl != null && dr != null) 
1512        return doEquivalent(dl,  dr);
1513    }
1514    return left.equals(right);
1515  }
1516
1517
1518
1519  private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1520    if (left.size() != right.size())
1521      return makeBoolean(false);
1522
1523    boolean res = true;
1524    for (int i = 0; i < left.size(); i++) {
1525      boolean found = false;
1526      for (int j = 0; j < right.size(); j++) {
1527        if (doEquivalent(left.get(i), right.get(j))) {
1528          found = true;
1529          break;
1530        }
1531      }
1532      if (!found) {
1533        res = false;
1534        break;
1535      }
1536    }
1537    return makeBoolean(res);
1538  }
1539
1540  private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1541    if (left.size() != right.size())
1542      return makeBoolean(true);
1543
1544    boolean res = true;
1545    for (int i = 0; i < left.size(); i++) {
1546      boolean found = false;
1547      for (int j = 0; j < right.size(); j++) {
1548        if (doEquivalent(left.get(i), right.get(j))) {
1549          found = true;
1550          break;
1551        }
1552      }
1553      if (!found) {
1554        res = false;
1555        break;
1556      }
1557    }
1558    return makeBoolean(!res);
1559  }
1560
1561        private List<Base> opLessThen(List<Base> left, List<Base> right) throws FHIRException {
1562    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1563      Base l = left.get(0);
1564      Base r = right.get(0);
1565      if (l.hasType("string") && r.hasType("string")) 
1566        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1567      else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 
1568        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
1569      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1570        return makeBoolean(compareDateTimeElements(l, r, false) < 0);
1571      else if ((l.hasType("time")) && (r.hasType("time"))) 
1572        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1573    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1574      List<Base> lUnit = left.get(0).listChildrenByName("code");
1575      List<Base> rUnit = right.get(0).listChildrenByName("code");
1576      if (Base.compareDeep(lUnit, rUnit, true)) {
1577        return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1578      } else {
1579        if (worker.getUcumService() == null)
1580          return makeBoolean(false);
1581        else {
1582          List<Base> dl = new ArrayList<Base>();
1583          dl.add(qtyToCanonical((Quantity) left.get(0)));
1584          List<Base> dr = new ArrayList<Base>();
1585          dr.add(qtyToCanonical((Quantity) right.get(0)));
1586          return opLessThen(dl, dr);
1587        }
1588      }
1589    }
1590    return new ArrayList<Base>();
1591  }
1592
1593        private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException {
1594    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1595      Base l = left.get(0);
1596      Base r = right.get(0);
1597      if (l.hasType("string") && r.hasType("string")) 
1598        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1599      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1600        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
1601      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1602        return makeBoolean(compareDateTimeElements(l, r, false) > 0);
1603      else if ((l.hasType("time")) && (r.hasType("time"))) 
1604        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1605    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1606      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1607      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1608      if (Base.compareDeep(lUnit, rUnit, true)) {
1609        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1610      } else {
1611        if (worker.getUcumService() == null)
1612          return makeBoolean(false);
1613        else {
1614          List<Base> dl = new ArrayList<Base>();
1615          dl.add(qtyToCanonical((Quantity) left.get(0)));
1616          List<Base> dr = new ArrayList<Base>();
1617          dr.add(qtyToCanonical((Quantity) right.get(0)));
1618          return opGreater(dl, dr);
1619        }
1620      }
1621    }
1622    return new ArrayList<Base>();
1623  }
1624
1625        private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1626    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1627      Base l = left.get(0);
1628      Base r = right.get(0);
1629      if (l.hasType("string") && r.hasType("string")) 
1630        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1631      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1632        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
1633      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1634        return makeBoolean(compareDateTimeElements(l, r, false) <= 0);
1635      else if ((l.hasType("time")) && (r.hasType("time"))) 
1636        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1637    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1638      List<Base> lUnits = left.get(0).listChildrenByName("unit");
1639      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
1640      List<Base> rUnits = right.get(0).listChildrenByName("unit");
1641      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
1642      if ((lunit == null && runit == null) || lunit.equals(runit)) {
1643        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1644      } else {
1645        if (worker.getUcumService() == null)
1646          return makeBoolean(false);
1647        else {
1648          List<Base> dl = new ArrayList<Base>();
1649          dl.add(qtyToCanonical((Quantity) left.get(0)));
1650          List<Base> dr = new ArrayList<Base>();
1651          dr.add(qtyToCanonical((Quantity) right.get(0)));
1652          return opLessOrEqual(dl, dr);
1653        }
1654      }
1655    }
1656    return new ArrayList<Base>();
1657  }
1658
1659        private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1660    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1661      Base l = left.get(0);
1662      Base r = right.get(0);
1663      if (l.hasType("string") && r.hasType("string")) 
1664        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1665      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1666        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
1667      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1668        return makeBoolean(compareDateTimeElements(l, r, false) >= 0);
1669      else if ((l.hasType("time")) && (r.hasType("time"))) 
1670        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1671    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1672      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1673      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1674      if (Base.compareDeep(lUnit, rUnit, true)) {
1675        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1676      } else {
1677        if (worker.getUcumService() == null)
1678          return makeBoolean(false);
1679        else {
1680          List<Base> dl = new ArrayList<Base>();
1681          dl.add(qtyToCanonical((Quantity) left.get(0)));
1682          List<Base> dr = new ArrayList<Base>();
1683          dr.add(qtyToCanonical((Quantity) right.get(0)));
1684          return opGreaterOrEqual(dl, dr);
1685        }
1686      }
1687    }
1688    return new ArrayList<Base>();
1689  }
1690
1691        private List<Base> opMemberOf(List<Base> left, List<Base> right) throws FHIRException {
1692          boolean ans = false;
1693          ValueSet vs = worker.fetchResource(ValueSet.class, right.get(0).primitiveValue());
1694          if (vs != null) {
1695            for (Base l : left) {
1696              if (l.fhirType().equals("code")) {
1697          if (worker.validateCode(l.castToCoding(l), vs).isOk())
1698            ans = true;
1699              } else if (l.fhirType().equals("Coding")) {
1700                if (worker.validateCode(l.castToCoding(l), vs).isOk())
1701                  ans = true;
1702              } else if (l.fhirType().equals("CodeableConcept")) {
1703                if (worker.validateCode(l.castToCodeableConcept(l), vs).isOk())
1704                  ans = true;
1705              }
1706            }
1707          }
1708          return makeBoolean(ans);
1709        }
1710
1711  private List<Base> opIn(List<Base> left, List<Base> right) throws FHIRException {
1712    boolean ans = true;
1713    for (Base l : left) {
1714      boolean f = false;
1715      for (Base r : right)
1716        if (doEquals(l, r)) {
1717          f = true;
1718          break;
1719        }
1720      if (!f) {
1721        ans = false;
1722        break;
1723      }
1724    }
1725    return makeBoolean(ans);
1726  }
1727
1728  private List<Base> opContains(List<Base> left, List<Base> right) {
1729    boolean ans = true;
1730    for (Base r : right) {
1731      boolean f = false;
1732      for (Base l : left)
1733        if (doEquals(l, r)) {
1734          f = true;
1735          break;
1736        }
1737      if (!f) {
1738        ans = false;
1739        break;
1740      }
1741    }
1742    return makeBoolean(ans);
1743  }
1744
1745  private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException {
1746    if (left.size() == 0)
1747      throw new PathEngineException("Error performing +: left operand has no value");
1748    if (left.size() > 1)
1749      throw new PathEngineException("Error performing +: left operand has more than one value");
1750    if (!left.get(0).isPrimitive())
1751      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1752    if (right.size() == 0)
1753      throw new PathEngineException("Error performing +: right operand has no value");
1754    if (right.size() > 1)
1755      throw new PathEngineException("Error performing +: right operand has more than one value");
1756    if (!right.get(0).isPrimitive())
1757      throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType()));
1758
1759    List<Base> result = new ArrayList<Base>();
1760    Base l = left.get(0);
1761    Base r = right.get(0);
1762    if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri")) 
1763      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
1764    else if (l.hasType("integer") && r.hasType("integer")) 
1765      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
1766    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
1767      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
1768    else
1769      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()));
1770    return result;
1771  }
1772
1773  private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException {
1774    if (left.size() == 0)
1775      throw new PathEngineException("Error performing *: left operand has no value");
1776    if (left.size() > 1)
1777      throw new PathEngineException("Error performing *: left operand has more than one value");
1778    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
1779      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1780    if (right.size() == 0)
1781      throw new PathEngineException("Error performing *: right operand has no value");
1782    if (right.size() > 1)
1783      throw new PathEngineException("Error performing *: right operand has more than one value");
1784    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
1785      throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType()));
1786
1787    List<Base> result = new ArrayList<Base>();
1788    Base l = left.get(0);
1789    Base r = right.get(0);
1790
1791    if (l.hasType("integer") && r.hasType("integer")) 
1792      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
1793    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
1794      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
1795    else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
1796      Pair pl = qtyToPair((Quantity) l);
1797      Pair pr = qtyToPair((Quantity) r);
1798      Pair p;
1799      try {
1800        p = worker.getUcumService().multiply(pl, pr);
1801        result.add(pairToQty(p));
1802      } catch (UcumException e) {
1803        throw new PathEngineException(e.getMessage(), e);
1804      }
1805    } else
1806      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()));
1807    return result;
1808  }
1809
1810
1811  private List<Base> opConcatenate(List<Base> left, List<Base> right) {
1812    List<Base> result = new ArrayList<Base>();
1813    result.add(new StringType(convertToString(left) + convertToString((right))));
1814    return result;
1815  }
1816
1817  private List<Base> opUnion(List<Base> left, List<Base> right) {
1818    List<Base> result = new ArrayList<Base>();
1819    for (Base item : left) {
1820      if (!doContains(result, item))
1821        result.add(item);
1822    }
1823    for (Base item : right) {
1824      if (!doContains(result, item))
1825        result.add(item);
1826    }
1827    return result;
1828  }
1829
1830  private boolean doContains(List<Base> list, Base item) {
1831    for (Base test : list)
1832      if (doEquals(test, item))
1833        return true;
1834    return false;
1835  }
1836
1837
1838  private List<Base> opAnd(List<Base> left, List<Base> right) {
1839    if (left.isEmpty() && right.isEmpty())
1840      return new ArrayList<Base>();
1841    else if (isBoolean(left, false) || isBoolean(right, false))
1842      return makeBoolean(false);
1843    else if (left.isEmpty() || right.isEmpty())
1844      return new ArrayList<Base>();
1845    else if (convertToBoolean(left) && convertToBoolean(right))
1846      return makeBoolean(true);
1847    else 
1848      return makeBoolean(false);
1849  }
1850
1851  private boolean isBoolean(List<Base> list, boolean b) {
1852    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
1853  }
1854
1855  private List<Base> opOr(List<Base> left, List<Base> right) {
1856    if (left.isEmpty() && right.isEmpty())
1857      return new ArrayList<Base>();
1858    else if (convertToBoolean(left) || convertToBoolean(right))
1859      return makeBoolean(true);
1860    else if (left.isEmpty() || right.isEmpty())
1861      return new ArrayList<Base>();
1862    else 
1863      return makeBoolean(false);
1864  }
1865
1866  private List<Base> opXor(List<Base> left, List<Base> right) {
1867    if (left.isEmpty() || right.isEmpty())
1868      return new ArrayList<Base>();
1869    else 
1870      return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right));
1871  }
1872
1873  private List<Base> opImplies(List<Base> left, List<Base> right) {
1874    if (!convertToBoolean(left)) 
1875      return makeBoolean(true);
1876    else if (right.size() == 0)
1877      return new ArrayList<Base>();      
1878    else
1879      return makeBoolean(convertToBoolean(right));
1880  }
1881
1882
1883  private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException {
1884    if (left.size() == 0)
1885      throw new PathEngineException("Error performing -: left operand has no value");
1886    if (left.size() > 1)
1887      throw new PathEngineException("Error performing -: left operand has more than one value");
1888    if (!left.get(0).isPrimitive())
1889      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1890    if (right.size() == 0)
1891      throw new PathEngineException("Error performing -: right operand has no value");
1892    if (right.size() > 1)
1893      throw new PathEngineException("Error performing -: right operand has more than one value");
1894    if (!right.get(0).isPrimitive())
1895      throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType()));
1896
1897    List<Base> result = new ArrayList<Base>();
1898    Base l = left.get(0);
1899    Base r = right.get(0);
1900
1901    if (l.hasType("integer") && r.hasType("integer")) 
1902      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
1903    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
1904      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
1905    else
1906      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()));
1907    return result;
1908  }
1909
1910  private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException {
1911    if (left.size() == 0)
1912      throw new PathEngineException("Error performing /: left operand has no value");
1913    if (left.size() > 1)
1914      throw new PathEngineException("Error performing /: left operand has more than one value");
1915    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
1916      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1917    if (right.size() == 0)
1918      throw new PathEngineException("Error performing /: right operand has no value");
1919    if (right.size() > 1)
1920      throw new PathEngineException("Error performing /: right operand has more than one value");
1921    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
1922      throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType()));
1923
1924    List<Base> result = new ArrayList<Base>();
1925    Base l = left.get(0);
1926    Base r = right.get(0);
1927
1928    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
1929      Decimal d1;
1930      try {
1931        d1 = new Decimal(l.primitiveValue());
1932        Decimal d2 = new Decimal(r.primitiveValue());
1933        result.add(new DecimalType(d1.divide(d2).asDecimal()));
1934      } catch (UcumException e) {
1935        throw new PathEngineException(e);
1936      }
1937    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
1938      Pair pl = qtyToPair((Quantity) l);
1939      Pair pr = qtyToPair((Quantity) r);
1940      Pair p;
1941      try {
1942        p = worker.getUcumService().multiply(pl, pr);
1943        result.add(pairToQty(p));
1944      } catch (UcumException e) {
1945        throw new PathEngineException(e.getMessage(), e);
1946      }
1947    } else
1948      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()));
1949    return result;
1950  }
1951
1952  private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException {
1953    if (left.size() == 0)
1954      throw new PathEngineException("Error performing div: left operand has no value");
1955    if (left.size() > 1)
1956      throw new PathEngineException("Error performing div: left operand has more than one value");
1957    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
1958      throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType()));
1959    if (right.size() == 0)
1960      throw new PathEngineException("Error performing div: right operand has no value");
1961    if (right.size() > 1)
1962      throw new PathEngineException("Error performing div: right operand has more than one value");
1963    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
1964      throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType()));
1965
1966    List<Base> result = new ArrayList<Base>();
1967    Base l = left.get(0);
1968    Base r = right.get(0);
1969
1970    if (l.hasType("integer") && r.hasType("integer")) 
1971      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue())));
1972    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
1973      Decimal d1;
1974      try {
1975        d1 = new Decimal(l.primitiveValue());
1976        Decimal d2 = new Decimal(r.primitiveValue());
1977        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
1978      } catch (UcumException e) {
1979        throw new PathEngineException(e);
1980      }
1981    }
1982    else
1983      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()));
1984    return result;
1985  }
1986
1987  private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException {
1988    if (left.size() == 0)
1989      throw new PathEngineException("Error performing mod: left operand has no value");
1990    if (left.size() > 1)
1991      throw new PathEngineException("Error performing mod: left operand has more than one value");
1992    if (!left.get(0).isPrimitive())
1993      throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType()));
1994    if (right.size() == 0)
1995      throw new PathEngineException("Error performing mod: right operand has no value");
1996    if (right.size() > 1)
1997      throw new PathEngineException("Error performing mod: right operand has more than one value");
1998    if (!right.get(0).isPrimitive())
1999      throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType()));
2000
2001    List<Base> result = new ArrayList<Base>();
2002    Base l = left.get(0);
2003    Base r = right.get(0);
2004
2005    if (l.hasType("integer") && r.hasType("integer")) 
2006      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue())));
2007    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
2008      Decimal d1;
2009      try {
2010        d1 = new Decimal(l.primitiveValue());
2011        Decimal d2 = new Decimal(r.primitiveValue());
2012        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
2013      } catch (UcumException e) {
2014        throw new PathEngineException(e);
2015      }
2016    }
2017    else
2018      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()));
2019    return result;
2020  }
2021
2022
2023  private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant) throws PathEngineException {
2024    if (constant instanceof BooleanType) 
2025      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2026    else if (constant instanceof IntegerType)
2027      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2028    else if (constant instanceof DecimalType)
2029      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
2030    else if (constant instanceof Quantity)
2031      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
2032    else if (constant instanceof FHIRConstant)
2033      return resolveConstantType(context, ((FHIRConstant) constant).getValue());
2034    else
2035      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2036  }
2037
2038  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException {
2039    if (s.startsWith("@")) {
2040      if (s.startsWith("@T"))
2041        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
2042      else
2043        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2044    } else if (s.equals("%sct"))
2045      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2046    else if (s.equals("%loinc"))
2047      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2048    else if (s.equals("%ucum"))
2049      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2050    else if (s.equals("%resource")) {
2051      if (context.resource == null)
2052        throw new PathEngineException("%resource cannot be used in this context");
2053      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
2054    } else if (s.equals("%context")) {
2055      return new TypeDetails(CollectionStatus.SINGLETON, context.context);
2056    } else if (s.equals("%map-codes"))
2057      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2058    else if (s.equals("%us-zip"))
2059      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2060    else if (s.startsWith("%\"vs-"))
2061      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2062    else if (s.startsWith("%\"cs-"))
2063      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2064    else if (s.startsWith("%\"ext-"))
2065      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2066    else if (hostServices == null)
2067      throw new PathEngineException("Unknown fixed constant type for '"+s+"'");
2068    else
2069      return hostServices.resolveConstantType(context.appInfo, s);
2070  }
2071
2072        private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
2073    List<Base> result = new ArrayList<Base>(); 
2074    if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
2075      if (item.isResource() && item.fhirType().equals(exp.getName()))  
2076        result.add(item);
2077    } else 
2078      getChildrenByName(item, exp.getName(), result);
2079    if (result.size() == 0 && atEntry && context.appInfo != null) {
2080      // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
2081      // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
2082      Base temp = hostServices.resolveConstant(context.appInfo, exp.getName());
2083      if (temp != null) {
2084        result.add(temp);
2085      }
2086    }
2087    return result;
2088  }     
2089
2090  private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException {
2091    if (hostServices == null)
2092      throw new PathEngineException("Unable to resolve context reference since no host services are provided");
2093    return hostServices.resolveConstantType(context.appInfo, name);
2094  }
2095  
2096  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
2097    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up
2098      return new TypeDetails(CollectionStatus.SINGLETON, type);
2099    TypeDetails result = new TypeDetails(null);
2100    getChildTypesByName(type, exp.getName(), result);
2101    return result;
2102  }
2103
2104
2105  private String hashTail(String type) {
2106    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
2107  }
2108
2109
2110  @SuppressWarnings("unchecked")
2111  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
2112    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
2113    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As)
2114      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
2115    else
2116      for (ExpressionNode expr : exp.getParameters()) {
2117        if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate)
2118          paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
2119        else
2120          paramTypes.add(executeType(context, focus, expr, true));
2121      }
2122    switch (exp.getFunction()) {
2123    case Empty : 
2124      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2125    case Not : 
2126      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2127    case Exists : 
2128      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2129    case SubsetOf : {
2130      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 
2131      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2132    }
2133    case SupersetOf : {
2134      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 
2135      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2136    }
2137    case IsDistinct : 
2138      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2139    case Distinct : 
2140      return focus;
2141    case Count : 
2142      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2143    case Where : 
2144      return focus;
2145    case Select : 
2146      return anything(focus.getCollectionStatus());
2147    case All : 
2148      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2149    case Repeat : 
2150      return anything(focus.getCollectionStatus());
2151    case Aggregate : 
2152      return anything(focus.getCollectionStatus());
2153    case Item : {
2154      checkOrdered(focus, "item");
2155      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2156      return focus; 
2157    }
2158    case As : {
2159      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2160      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
2161    }
2162    case OfType : { 
2163      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2164      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
2165    }
2166    case Type : { 
2167      boolean s = false;
2168      boolean c = false;
2169      for (ProfiledType pt : focus.getProfiledTypes()) {
2170        s = s || pt.isSystemType();
2171        c = c || !pt.isSystemType();
2172      }
2173      if (s && c)
2174        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo);
2175      else if (s)
2176        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo);
2177      else
2178        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo);
2179    }
2180    case Is : {
2181      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2182      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2183    }
2184    case Single :
2185      return focus.toSingleton();
2186    case First : {
2187      checkOrdered(focus, "first");
2188      return focus.toSingleton();
2189    }
2190    case Last : {
2191      checkOrdered(focus, "last");
2192      return focus.toSingleton();
2193    }
2194    case Tail : {
2195      checkOrdered(focus, "tail");
2196      return focus;
2197    }
2198    case Skip : {
2199      checkOrdered(focus, "skip");
2200      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2201      return focus;
2202    }
2203    case Take : {
2204      checkOrdered(focus, "take");
2205      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2206      return focus;
2207    }
2208    case Union : {
2209      return focus.union(paramTypes.get(0));
2210    }
2211    case Combine : {
2212      return focus.union(paramTypes.get(0));
2213    }
2214    case Intersect : {
2215      return focus.intersect(paramTypes.get(0));
2216    }
2217    case Exclude : {
2218      return focus;
2219    }
2220    case Iif : {
2221      TypeDetails types = new TypeDetails(null);
2222      types.update(paramTypes.get(0));
2223      if (paramTypes.size() > 1)
2224        types.update(paramTypes.get(1));
2225      return types;
2226    }
2227    case Lower : {
2228      checkContextString(focus, "lower");
2229      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2230    }
2231    case Upper : {
2232      checkContextString(focus, "upper");
2233      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2234    }
2235    case ToChars : {
2236      checkContextString(focus, "toChars");
2237      return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 
2238    }
2239    case Substring : {
2240      checkContextString(focus, "subString");
2241      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2242      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2243    }
2244    case StartsWith : {
2245      checkContextString(focus, "startsWith");
2246      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2247      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2248    }
2249    case EndsWith : {
2250      checkContextString(focus, "endsWith");
2251      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2252      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2253    }
2254    case Matches : {
2255      checkContextString(focus, "matches");
2256      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2257      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2258    }
2259    case ReplaceMatches : {
2260      checkContextString(focus, "replaceMatches");
2261      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2262      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2263    }
2264    case Contains : {
2265      checkContextString(focus, "contains");
2266      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2267      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2268    }
2269    case Replace : {
2270      checkContextString(focus, "replace");
2271      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2272      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2273    }
2274    case Length : { 
2275      checkContextPrimitive(focus, "length", false);
2276      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2277    }
2278    case Children : 
2279      return childTypes(focus, "*");
2280    case Descendants : 
2281      return childTypes(focus, "**");
2282    case MemberOf : {
2283      checkContextCoded(focus, "memberOf");
2284      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2285      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2286    }
2287    case Trace : {
2288      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2289      return focus; 
2290    }
2291    case Today : 
2292      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2293    case Now : 
2294      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2295    case Resolve : {
2296      checkContextReference(focus, "resolve");
2297      return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 
2298    }
2299    case Extension : {
2300      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2301      return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 
2302    }
2303    case HasValue : 
2304      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2305    case HtmlChecks : 
2306      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2307    case Alias : 
2308      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2309      return anything(CollectionStatus.SINGLETON); 
2310    case AliasAs : 
2311      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2312      return focus; 
2313    case ToInteger : {
2314      checkContextPrimitive(focus, "toInteger", true);
2315      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2316    }
2317    case ToDecimal : {
2318      checkContextPrimitive(focus, "toDecimal", true);
2319      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
2320    }
2321    case ToString : {
2322      checkContextPrimitive(focus, "toString", true);
2323      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2324    }
2325    case ToQuantity : {
2326      checkContextPrimitive(focus, "toQuantity", true);
2327      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
2328    }
2329    case ToBoolean : {
2330      checkContextPrimitive(focus, "toBoolean", false);
2331      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2332    }
2333    case ToDateTime : {
2334      checkContextPrimitive(focus, "toBoolean", false);
2335      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2336    }
2337    case ToTime : {
2338      checkContextPrimitive(focus, "toBoolean", false);
2339      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
2340    }
2341    case IsString : 
2342    case IsQuantity :{
2343      checkContextPrimitive(focus, exp.getFunction().toCode(), true);
2344      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2345    } 
2346    case IsInteger : 
2347    case IsDecimal : 
2348    case IsDateTime : 
2349    case IsTime : 
2350    case IsBoolean : {
2351      checkContextPrimitive(focus, exp.getFunction().toCode(), false);
2352      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2353    }
2354    case ConformsTo: {
2355      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2356      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);       
2357    }
2358    case Custom : {
2359      return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
2360    }
2361    default:
2362      break;
2363    }
2364    throw new Error("not Implemented yet");
2365  }
2366
2367
2368  private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
2369    int i = 0;
2370    for (TypeDetails pt : typeSet) {
2371      if (i == paramTypes.size())
2372        return;
2373      TypeDetails actual = paramTypes.get(i);
2374      i++;
2375      for (String a : actual.getTypes()) {
2376        if (!pt.hasType(worker, a))
2377          throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 
2378      }
2379    }
2380  }
2381
2382  private void checkOrdered(TypeDetails focus, String name) throws PathEngineException {
2383    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED)
2384      throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 
2385  }
2386
2387  private void checkContextReference(TypeDetails focus, String name) throws PathEngineException {
2388    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical"))
2389      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, canonical, Reference"); 
2390  }
2391
2392
2393  private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException {
2394    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept"))
2395      throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept");     
2396  }
2397
2398
2399  private void checkContextString(TypeDetails focus, String name) throws PathEngineException {
2400    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id"))
2401      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 
2402  }
2403
2404
2405  private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty) throws PathEngineException {
2406    if (canQty) {
2407       if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity"))
2408        throw new PathEngineException("The function '"+name+"'() can only be used on a Quantity or on "+primitiveTypes.toString()); 
2409    } else if (!focus.hasType(primitiveTypes))
2410      throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()); 
2411  }
2412
2413
2414  private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException {
2415    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
2416    for (String f : focus.getTypes()) 
2417      getChildTypesByName(f, mask, result);
2418    return result;
2419  }
2420
2421  private TypeDetails anything(CollectionStatus status) {
2422    return new TypeDetails(status, allTypes.keySet());
2423  }
2424
2425  //    private boolean isPrimitiveType(String s) {
2426  //            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");
2427  //    }
2428
2429        private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2430    switch (exp.getFunction()) {
2431    case Empty : return funcEmpty(context, focus, exp);
2432    case Not : return funcNot(context, focus, exp);
2433    case Exists : return funcExists(context, focus, exp);
2434    case SubsetOf : return funcSubsetOf(context, focus, exp);
2435    case SupersetOf : return funcSupersetOf(context, focus, exp);
2436    case IsDistinct : return funcIsDistinct(context, focus, exp);
2437    case Distinct : return funcDistinct(context, focus, exp);
2438    case Count : return funcCount(context, focus, exp);
2439    case Where : return funcWhere(context, focus, exp);
2440    case Select : return funcSelect(context, focus, exp);
2441    case All : return funcAll(context, focus, exp);
2442    case Repeat : return funcRepeat(context, focus, exp);
2443    case Aggregate : return funcAggregate(context, focus, exp);
2444    case Item : return funcItem(context, focus, exp);
2445    case As : return funcAs(context, focus, exp);
2446    case OfType : return funcAs(context, focus, exp);
2447    case Type : return funcType(context, focus, exp);
2448    case Is : return funcIs(context, focus, exp);
2449    case Single : return funcSingle(context, focus, exp);
2450    case First : return funcFirst(context, focus, exp);
2451    case Last : return funcLast(context, focus, exp);
2452    case Tail : return funcTail(context, focus, exp);
2453    case Skip : return funcSkip(context, focus, exp);
2454    case Take : return funcTake(context, focus, exp);
2455    case Union : return funcUnion(context, focus, exp);
2456    case Combine : return funcCombine(context, focus, exp);
2457    case Intersect : return funcIntersect(context, focus, exp);
2458    case Exclude : return funcExclude(context, focus, exp);
2459    case Iif : return funcIif(context, focus, exp);
2460    case Lower : return funcLower(context, focus, exp);
2461    case Upper : return funcUpper(context, focus, exp);
2462    case ToChars : return funcToChars(context, focus, exp);
2463    case Substring : return funcSubstring(context, focus, exp);
2464    case StartsWith : return funcStartsWith(context, focus, exp);
2465    case EndsWith : return funcEndsWith(context, focus, exp);
2466    case Matches : return funcMatches(context, focus, exp);
2467    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
2468    case Contains : return funcContains(context, focus, exp);
2469    case Replace : return funcReplace(context, focus, exp);
2470    case Length : return funcLength(context, focus, exp);
2471    case Children : return funcChildren(context, focus, exp);
2472    case Descendants : return funcDescendants(context, focus, exp);
2473    case MemberOf : return funcMemberOf(context, focus, exp);
2474    case Trace : return funcTrace(context, focus, exp);
2475    case Today : return funcToday(context, focus, exp);
2476    case Now : return funcNow(context, focus, exp);
2477    case Resolve : return funcResolve(context, focus, exp);
2478    case Extension : return funcExtension(context, focus, exp);
2479    case HasValue : return funcHasValue(context, focus, exp);
2480    case AliasAs : return funcAliasAs(context, focus, exp);
2481    case Alias : return funcAlias(context, focus, exp);
2482    case HtmlChecks : return funcHtmlChecks(context, focus, exp);
2483    case ToInteger : return funcToInteger(context, focus, exp);
2484    case ToDecimal : return funcToDecimal(context, focus, exp);
2485    case ToString : return funcToString(context, focus, exp);
2486    case ToBoolean : return funcToBoolean(context, focus, exp);
2487    case ToQuantity : return funcToQuantity(context, focus, exp);
2488    case ToDateTime : return funcToDateTime(context, focus, exp);
2489    case ToTime : return funcToTime(context, focus, exp);
2490    case IsInteger : return funcIsInteger(context, focus, exp);
2491    case IsDecimal : return funcIsDecimal(context, focus, exp);
2492    case IsString : return funcIsString(context, focus, exp);
2493    case IsBoolean : return funcIsBoolean(context, focus, exp);
2494    case IsQuantity : return funcIsQuantity(context, focus, exp);
2495    case IsDateTime : return funcIsDateTime(context, focus, exp);
2496    case IsTime : return funcIsTime(context, focus, exp);
2497    case ConformsTo : return funcConformsTo(context, focus, exp); 
2498    case Custom: { 
2499      List<List<Base>> params = new ArrayList<List<Base>>();
2500      for (ExpressionNode p : exp.getParameters()) 
2501        params.add(execute(context, focus, p, true));
2502      return hostServices.executeFunction(context.appInfo, exp.getName(), params);
2503    }
2504    default:
2505      throw new Error("not Implemented yet");
2506    }
2507  }
2508
2509        private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2510    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2511    String name = nl.get(0).primitiveValue();
2512    context.addAlias(name, focus);
2513    return focus;
2514  }
2515
2516  private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2517    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2518    String name = nl.get(0).primitiveValue();
2519    List<Base> res = new ArrayList<Base>();
2520    Base b = context.getAlias(name);
2521    if (b != null)
2522      res.add(b);
2523    return res;    
2524  }
2525
2526  private List<Base> funcHtmlChecks(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2527    // todo: actually check the HTML
2528    return makeBoolean(true);    
2529  }
2530
2531
2532  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2533    if (exp.getParameters().size() == 1) {
2534      List<Base> result = new ArrayList<Base>();
2535      List<Base> pc = new ArrayList<Base>();
2536      boolean all = true;
2537      for (Base item : focus) {
2538        pc.clear();
2539        pc.add(item);
2540        if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) {
2541          all = false;
2542          break;
2543        }
2544      }
2545      result.add(new BooleanType(all).noExtensions());
2546      return result;
2547    } else {// (exp.getParameters().size() == 0) {
2548      List<Base> result = new ArrayList<Base>();
2549      boolean all = true;
2550      for (Base item : focus) {
2551        boolean v = false;
2552        if (item instanceof BooleanType) {
2553          v = ((BooleanType) item).booleanValue();
2554        } else 
2555          v = item != null;
2556        if (!v) {
2557          all = false;
2558          break;
2559        }
2560      }
2561      result.add(new BooleanType(all).noExtensions());
2562      return result;
2563    }
2564  }
2565
2566
2567  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
2568    return new ExecutionContext(context.appInfo, context.resource, context.context, context.aliases, newThis);
2569  }
2570
2571  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
2572    return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
2573  }
2574
2575
2576  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2577    List<Base> result = new ArrayList<Base>();
2578    result.add(DateTimeType.now());
2579    return result;
2580  }
2581
2582
2583  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2584    List<Base> result = new ArrayList<Base>();
2585    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
2586    return result;
2587  }
2588
2589
2590  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2591    throw new Error("not Implemented yet");
2592  }
2593
2594
2595  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2596    List<Base> result = new ArrayList<Base>();
2597    List<Base> current = new ArrayList<Base>();
2598    current.addAll(focus);
2599    List<Base> added = new ArrayList<Base>();
2600    boolean more = true;
2601    while (more) {
2602      added.clear();
2603      for (Base item : current) {
2604        getChildrenByName(item, "*", added);
2605      }
2606      more = !added.isEmpty();
2607      result.addAll(added);
2608      current.clear();
2609      current.addAll(added);
2610    }
2611    return result;
2612  }
2613
2614
2615  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2616    List<Base> result = new ArrayList<Base>();
2617    for (Base b : focus)
2618      getChildrenByName(b, "*", result);
2619    return result;
2620  }
2621
2622
2623  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException {
2624    List<Base> result = new ArrayList<Base>();
2625
2626    if (focus.size() == 1) {
2627      String f = convertToString(focus.get(0));
2628
2629      if (!Utilities.noString(f)) {
2630
2631        if (exp.getParameters().size() != 2) {
2632
2633          String t = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2634          String r = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2635
2636          String n = f.replace(t, r);
2637          result.add(new StringType(n));
2638        }
2639        else {
2640          throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size()));
2641        }
2642      }
2643      else {
2644        throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item"));
2645      }
2646    }
2647    else {
2648      throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size()));
2649    }
2650    return result;
2651  }
2652
2653
2654  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2655    List<Base> result = new ArrayList<Base>();
2656    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2657
2658    if (focus.size() == 1 && !Utilities.noString(sw))
2659      result.add(new BooleanType(convertToString(focus.get(0)).contains(sw)).noExtensions());
2660    else
2661      result.add(new BooleanType(false).noExtensions());
2662    return result;
2663  }
2664
2665
2666  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2667    List<Base> result = new ArrayList<Base>();
2668    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2669
2670    if (focus.size() == 1 && !Utilities.noString(sw))
2671      result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
2672    else
2673      result.add(new BooleanType(false).noExtensions());
2674    return result;
2675  }
2676
2677
2678  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2679    List<Base> result = new ArrayList<Base>();
2680    result.add(new StringType(convertToString(focus)).noExtensions());
2681    return result;
2682  }
2683
2684  private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2685    List<Base> result = new ArrayList<Base>();
2686    if (focus.size() == 1) {
2687      if (focus.get(0) instanceof BooleanType)
2688        result.add(focus.get(0));
2689      else if (focus.get(0) instanceof IntegerType)
2690        result.add(new BooleanType(!focus.get(0).primitiveValue().equals("0")).noExtensions());
2691      else if (focus.get(0) instanceof StringType) {
2692        if ("true".equals(focus.get(0).primitiveValue()))
2693          result.add(new BooleanType(true).noExtensions());
2694        else if ("false".equals(focus.get(0).primitiveValue()))
2695          result.add(new BooleanType(false).noExtensions()); 
2696      }
2697    }
2698    return result;
2699  }
2700
2701  private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2702    List<Base> result = new ArrayList<Base>();
2703    if (focus.size() == 1) {
2704      if (focus.get(0) instanceof Quantity) 
2705        result.add(focus.get(0));
2706      else if (focus.get(0) instanceof StringType) {
2707        Quantity q = parseQuantityString(focus.get(0).primitiveValue());
2708        if (q != null)
2709          result.add(q.noExtensions());
2710      } else if (focus.get(0) instanceof IntegerType) {
2711        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
2712      } else if (focus.get(0) instanceof DecimalType) {
2713        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
2714      }
2715    }
2716    return result;
2717  }
2718
2719  private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2720//  List<Base> result = new ArrayList<Base>();
2721//  result.add(new BooleanType(convertToBoolean(focus)));
2722//  return result;
2723  throw new NotImplementedException("funcToDateTime is not implemented");
2724}
2725
2726  private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2727//  List<Base> result = new ArrayList<Base>();
2728//  result.add(new BooleanType(convertToBoolean(focus)));
2729//  return result;
2730  throw new NotImplementedException("funcToTime is not implemented");
2731}
2732
2733
2734  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2735    String s = convertToString(focus);
2736    List<Base> result = new ArrayList<Base>();
2737    if (Utilities.isDecimal(s))
2738      result.add(new DecimalType(s).noExtensions());
2739    if ("true".equals(s))
2740      result.add(new DecimalType(1).noExtensions());
2741    if ("false".equals(s))
2742      result.add(new DecimalType(0).noExtensions());
2743    return result;
2744  }
2745
2746
2747  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2748    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2749    Boolean v = convertToBoolean(n1);
2750
2751    if (v)
2752      return execute(context, focus, exp.getParameters().get(1), true);
2753    else if (exp.getParameters().size() < 3)
2754      return new ArrayList<Base>();
2755    else
2756      return execute(context, focus, exp.getParameters().get(2), true);
2757  }
2758
2759
2760  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2761    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2762    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2763
2764    List<Base> result = new ArrayList<Base>();
2765    for (int i = 0; i < Math.min(focus.size(), i1); i++)
2766      result.add(focus.get(i));
2767    return result;
2768  }
2769
2770
2771  private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2772    List<Base> result = new ArrayList<Base>();
2773    for (Base item : focus) {
2774      if (!doContains(result, item))
2775        result.add(item);
2776    }
2777    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
2778      if (!doContains(result, item))
2779        result.add(item);
2780    }
2781    return result;
2782  }
2783
2784  private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2785    List<Base> result = new ArrayList<Base>();
2786    for (Base item : focus) {
2787      result.add(item);
2788    }
2789    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
2790      result.add(item);
2791    }
2792    return result;
2793  }
2794
2795  private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2796    List<Base> result = new ArrayList<Base>();
2797    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
2798    
2799    for (Base item : focus) {
2800      if (!doContains(result, item) && doContains(other, item))
2801        result.add(item);
2802    }
2803    return result;    
2804  }
2805
2806  private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2807    List<Base> result = new ArrayList<Base>();
2808    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
2809    
2810    for (Base item : focus) {
2811      if (!doContains(result, item) && !doContains(other, item))
2812        result.add(item);
2813    }
2814    return result;
2815  }
2816
2817
2818  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2819    if (focus.size() == 1)
2820      return focus;
2821    throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()));
2822  }
2823
2824
2825  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2826    if (focus.size() == 0 || focus.size() > 1) 
2827      return makeBoolean(false);
2828    String ns = null;
2829    String n = null;
2830    
2831    ExpressionNode texp = exp.getParameters().get(0);
2832    if (texp.getKind() != Kind.Name)
2833      throw new PathEngineException("Unsupported Expression type for Parameter on Is");
2834    if (texp.getInner() != null) {
2835      if (texp.getInner().getKind() != Kind.Name)
2836        throw new PathEngineException("Unsupported Expression type for Parameter on Is");
2837      ns = texp.getName();
2838      n = texp.getInner().getName();
2839    } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) {
2840      ns = "System";
2841      n = texp.getName();
2842    } else {
2843      ns = "FHIR";
2844      n = texp.getName();        
2845    }
2846    if (ns.equals("System")) {
2847      if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions())
2848        return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType())));
2849      else
2850        return makeBoolean(false);
2851    } else if (ns.equals("FHIR")) {
2852      return makeBoolean(n.equals(focus.get(0).fhirType()));
2853    } else { 
2854      return makeBoolean(false);
2855    }
2856  }
2857
2858
2859  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2860    List<Base> result = new ArrayList<Base>();
2861    String tn = exp.getParameters().get(0).getName();
2862    for (Base b : focus)
2863      if (b.hasType(tn))
2864        result.add(b);
2865    return result;
2866  }
2867
2868  private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2869    List<Base> result = new ArrayList<Base>();
2870    for (Base item : focus)
2871      result.add(new ClassTypeInfo(item));
2872    return result;
2873  }
2874
2875
2876  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2877    List<Base> result = new ArrayList<Base>();
2878    List<Base> current = new ArrayList<Base>();
2879    current.addAll(focus);
2880    List<Base> added = new ArrayList<Base>();
2881    boolean more = true;
2882    while (more) {
2883      added.clear();
2884      List<Base> pc = new ArrayList<Base>();
2885      for (Base item : current) {
2886        pc.clear();
2887        pc.add(item);
2888        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
2889      }
2890      more = !added.isEmpty();
2891      result.addAll(added);
2892      current.clear();
2893      current.addAll(added);
2894    }
2895    return result;
2896  }
2897
2898
2899  private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2900    List<Base> total = new ArrayList<Base>();
2901    if (exp.parameterCount() > 1)
2902      total = execute(context, focus, exp.getParameters().get(1), false);
2903
2904    List<Base> pc = new ArrayList<Base>();
2905    for (Base item : focus) {
2906      ExecutionContext c = changeThis(context, item);
2907      c.total = total;
2908      total = execute(c, pc, exp.getParameters().get(0), true);
2909    }
2910    return total;
2911  }
2912
2913
2914
2915  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2916    if (focus.size() <= 1)
2917      return makeBoolean(true);
2918
2919    boolean distinct = true;
2920    for (int i = 0; i < focus.size(); i++) {
2921      for (int j = i+1; j < focus.size(); j++) {
2922        if (doEquals(focus.get(j), focus.get(i))) {
2923          distinct = false;
2924          break;
2925        }
2926      }
2927    }
2928    return makeBoolean(distinct);
2929  }
2930
2931
2932  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2933    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2934
2935    boolean valid = true;
2936    for (Base item : target) {
2937      boolean found = false;
2938      for (Base t : focus) {
2939        if (Base.compareDeep(item, t, false)) {
2940          found = true;
2941          break;
2942        }
2943      }
2944      if (!found) {
2945        valid = false;
2946        break;
2947      }
2948    }
2949    List<Base> result = new ArrayList<Base>();
2950    result.add(new BooleanType(valid).noExtensions());
2951    return result;
2952  }
2953
2954
2955  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2956    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2957
2958    boolean valid = true;
2959    for (Base item : focus) {
2960      boolean found = false;
2961      for (Base t : target) {
2962        if (Base.compareDeep(item, t, false)) {
2963          found = true;
2964          break;
2965        }
2966      }
2967      if (!found) {
2968        valid = false;
2969        break;
2970      }
2971    }
2972    List<Base> result = new ArrayList<Base>();
2973    result.add(new BooleanType(valid).noExtensions());
2974    return result;
2975  }
2976
2977
2978  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2979    List<Base> result = new ArrayList<Base>();
2980    result.add(new BooleanType(!ElementUtil.isEmpty(focus)).noExtensions());
2981    return result;
2982  }
2983
2984
2985  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2986    List<Base> result = new ArrayList<Base>();
2987    for (Base item : focus) {
2988      String s = convertToString(item);
2989      if (item.fhirType().equals("Reference")) {
2990        Property p = item.getChildByName("reference");
2991        if (p != null && p.hasValues())
2992          s = convertToString(p.getValues().get(0));
2993        else
2994          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
2995      }
2996      if (item.fhirType().equals("canonical")) {
2997        s = item.primitiveValue();
2998      }
2999      if (s != null) {
3000        Base res = null;
3001        if (s.startsWith("#")) {
3002          Property p = context.resource.getChildByName("contained");
3003          for (Base c : p.getValues()) {
3004            if (s.equals(c.getIdBase())) {
3005              res = c;
3006              break;
3007            }
3008          }
3009        } else if (hostServices != null) {
3010          res = hostServices.resolveReference(context.appInfo, s);
3011        }
3012        if (res != null)
3013          result.add(res);
3014      }
3015    }
3016
3017    return result;
3018  }
3019
3020        private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3021    List<Base> result = new ArrayList<Base>();
3022    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3023    String url = nl.get(0).primitiveValue();
3024
3025    for (Base item : focus) {
3026      List<Base> ext = new ArrayList<Base>();
3027      getChildrenByName(item, "extension", ext);
3028      getChildrenByName(item, "modifierExtension", ext);
3029      for (Base ex : ext) {
3030        List<Base> vl = new ArrayList<Base>();
3031        getChildrenByName(ex, "url", vl);
3032        if (convertToString(vl).equals(url))
3033          result.add(ex);
3034      }
3035    }
3036    return result;
3037  }
3038
3039        private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3040    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3041    String name = nl.get(0).primitiveValue();
3042    if (exp.getParameters().size() == 2) {
3043      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
3044      log(name, n2);
3045    } else 
3046      log(name, focus);
3047    return focus;
3048  }
3049
3050  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3051    if (focus.size() <= 1)
3052      return focus;
3053
3054    List<Base> result = new ArrayList<Base>();
3055    for (int i = 0; i < focus.size(); i++) {
3056      boolean found = false;
3057      for (int j = i+1; j < focus.size(); j++) {
3058        if (doEquals(focus.get(j), focus.get(i))) {
3059          found = true;
3060          break;
3061        }
3062      }
3063      if (!found)
3064        result.add(focus.get(i));
3065    }
3066    return result;
3067  }
3068
3069        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3070    List<Base> result = new ArrayList<Base>();
3071    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3072
3073    if (focus.size() == 1 && !Utilities.noString(sw)) {
3074      String st = convertToString(focus.get(0));
3075      if (Utilities.noString(st))
3076        result.add(new BooleanType(false).noExtensions());
3077      else
3078        result.add(new BooleanType(st.matches(sw)).noExtensions());
3079    } else
3080      result.add(new BooleanType(false).noExtensions());
3081    return result;
3082  }
3083
3084        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3085    List<Base> result = new ArrayList<Base>();
3086    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3087
3088    if (focus.size() == 1 && !Utilities.noString(sw)) {
3089      String st = convertToString(focus.get(0));
3090      if (Utilities.noString(st))
3091        result.add(new BooleanType(false).noExtensions());
3092      else
3093        result.add(new BooleanType(st.contains(sw)).noExtensions());
3094    }  else
3095      result.add(new BooleanType(false).noExtensions());
3096    return result;
3097  }
3098
3099  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3100    List<Base> result = new ArrayList<Base>();
3101    if (focus.size() == 1) {
3102      String s = convertToString(focus.get(0));
3103      result.add(new IntegerType(s.length()).noExtensions());
3104    }
3105    return result;
3106  }
3107
3108  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3109    List<Base> result = new ArrayList<Base>();
3110    if (focus.size() == 1) {
3111      String s = convertToString(focus.get(0));
3112      result.add(new BooleanType(!Utilities.noString(s)).noExtensions());
3113    }
3114    return result;
3115  }
3116
3117        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3118    List<Base> result = new ArrayList<Base>();
3119    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3120
3121    if (focus.size() == 1 && !Utilities.noString(sw))
3122      result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw)).noExtensions());
3123    else
3124      result.add(new BooleanType(false).noExtensions());
3125    return result;
3126  }
3127
3128  private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3129    List<Base> result = new ArrayList<Base>();
3130    if (focus.size() == 1) {
3131      String s = convertToString(focus.get(0));
3132      if (!Utilities.noString(s)) 
3133        result.add(new StringType(s.toLowerCase()).noExtensions());
3134    }
3135    return result;
3136  }
3137
3138  private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3139    List<Base> result = new ArrayList<Base>();
3140    if (focus.size() == 1) {
3141      String s = convertToString(focus.get(0));
3142      if (!Utilities.noString(s)) 
3143        result.add(new StringType(s.toUpperCase()).noExtensions());
3144    }
3145    return result;
3146  }
3147
3148  private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3149    List<Base> result = new ArrayList<Base>();
3150    if (focus.size() == 1) {
3151      String s = convertToString(focus.get(0));
3152      for (char c : s.toCharArray())  
3153        result.add(new StringType(String.valueOf(c)).noExtensions());
3154    }
3155    return result;
3156  }
3157
3158        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3159    List<Base> result = new ArrayList<Base>();
3160    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3161    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
3162    int i2 = -1;
3163    if (exp.parameterCount() == 2) {
3164      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
3165      i2 = Integer.parseInt(n2.get(0).primitiveValue());
3166    }
3167
3168    if (focus.size() == 1) {
3169      String sw = convertToString(focus.get(0));
3170      String s;
3171      if (i1 < 0 || i1 >= sw.length())
3172        return new ArrayList<Base>();
3173      if (exp.parameterCount() == 2)
3174        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
3175      else
3176        s = sw.substring(i1);
3177      if (!Utilities.noString(s)) 
3178        result.add(new StringType(s).noExtensions());
3179    }
3180    return result;
3181  }
3182
3183  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3184    String s = convertToString(focus);
3185    List<Base> result = new ArrayList<Base>();
3186    if (Utilities.isInteger(s))
3187      result.add(new IntegerType(s).noExtensions());
3188    else if ("true".equals(s))
3189      result.add(new IntegerType(1).noExtensions());
3190    else if ("false".equals(s))
3191      result.add(new IntegerType(0).noExtensions());
3192    return result;
3193  }
3194
3195  private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3196    List<Base> result = new ArrayList<Base>();
3197    if (focus.size() != 1)
3198      result.add(new BooleanType(false).noExtensions());
3199    else if (focus.get(0) instanceof IntegerType)
3200      result.add(new BooleanType(true).noExtensions());
3201    else if (focus.get(0) instanceof BooleanType)
3202      result.add(new BooleanType(true).noExtensions());
3203    else if (focus.get(0) instanceof StringType)
3204      result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions());
3205    else 
3206      result.add(new BooleanType(false).noExtensions());
3207    return result;
3208  }
3209
3210  private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3211    List<Base> result = new ArrayList<Base>();
3212    if (focus.size() != 1)
3213      result.add(new BooleanType(false).noExtensions());
3214    else if (focus.get(0) instanceof IntegerType && ((IntegerType) focus.get(0)).getValue() >= 0)
3215      result.add(new BooleanType(true).noExtensions());
3216    else if (focus.get(0) instanceof BooleanType)
3217      result.add(new BooleanType(true).noExtensions());
3218    else if (focus.get(0) instanceof StringType)
3219      result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)), "true", "false")).noExtensions());
3220    else 
3221      result.add(new BooleanType(false).noExtensions());
3222    return result;
3223  }
3224
3225  private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3226    List<Base> result = new ArrayList<Base>();
3227    if (focus.size() != 1)
3228      result.add(new BooleanType(false).noExtensions());
3229    else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType)
3230      result.add(new BooleanType(true).noExtensions());
3231    else if (focus.get(0) instanceof StringType)
3232      result.add(new BooleanType((convertToString(focus.get(0)).matches
3233          ("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"))).noExtensions());
3234    else 
3235      result.add(new BooleanType(false).noExtensions());
3236    return result;
3237  }
3238
3239  private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3240    if (hostServices == null)
3241      throw new FHIRException("Unable to check conformsTo - no hostservices provided");
3242    List<Base> result = new ArrayList<Base>();
3243    if (focus.size() != 1)
3244      result.add(new BooleanType(false).noExtensions());
3245    else {
3246      String url = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3247      result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo,  focus.get(0), url)).noExtensions());
3248    }
3249    return result;
3250  }
3251
3252  private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3253    List<Base> result = new ArrayList<Base>();
3254    if (focus.size() != 1)
3255      result.add(new BooleanType(false).noExtensions());
3256    else if (focus.get(0) instanceof TimeType)
3257      result.add(new BooleanType(true).noExtensions());
3258    else if (focus.get(0) instanceof StringType)
3259      result.add(new BooleanType((convertToString(focus.get(0)).matches
3260          ("T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?"))).noExtensions());
3261    else 
3262      result.add(new BooleanType(false).noExtensions());
3263    return result;
3264  }
3265
3266  private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3267    List<Base> result = new ArrayList<Base>();
3268    if (focus.size() != 1)
3269      result.add(new BooleanType(false).noExtensions());
3270    else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType))
3271      result.add(new BooleanType(true).noExtensions());
3272    else 
3273      result.add(new BooleanType(false).noExtensions());
3274    return result;
3275  }
3276
3277  private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3278    List<Base> result = new ArrayList<Base>();
3279    if (focus.size() != 1)
3280      result.add(new BooleanType(false).noExtensions());
3281    else if (focus.get(0) instanceof IntegerType)
3282      result.add(new BooleanType(true).noExtensions());
3283    else if (focus.get(0) instanceof DecimalType)
3284      result.add(new BooleanType(true).noExtensions());
3285    else if (focus.get(0) instanceof Quantity)
3286      result.add(new BooleanType(true).noExtensions());
3287    else  if (focus.get(0) instanceof StringType) {
3288      Quantity q = parseQuantityString(focus.get(0).primitiveValue());
3289      result.add(new BooleanType(q != null).noExtensions());
3290    } else
3291      result.add(new BooleanType(false).noExtensions());
3292    return result;
3293  }
3294
3295  public Quantity parseQuantityString(String s) {
3296    if (s == null)
3297      return null;
3298    s = s.trim();
3299    if (s.contains(" ")) {
3300      String v = s.substring(0, s.indexOf(" ")).trim();
3301      s = s.substring(s.indexOf(" ")).trim();
3302      if (!Utilities.isDecimal(v))
3303        return null;
3304      if (s.startsWith("'") && s.endsWith("'"))
3305        return Quantity.fromUcum(v, s.substring(1, s.length()-1));
3306      if (s.equals("year") || s.equals("years"))
3307        return Quantity.fromUcum(v, "a");
3308      else if (s.equals("month") || s.equals("months"))
3309        return Quantity.fromUcum(v, "mo");
3310      else if (s.equals("week") || s.equals("weeks"))
3311        return Quantity.fromUcum(v, "wk");
3312      else if (s.equals("day") || s.equals("days"))
3313        return Quantity.fromUcum(v, "d");
3314      else if (s.equals("hour") || s.equals("hours"))
3315        return Quantity.fromUcum(v, "h");
3316      else if (s.equals("minute") || s.equals("minutes"))
3317        return Quantity.fromUcum(v, "min");
3318      else if (s.equals("second") || s.equals("seconds"))
3319        return Quantity.fromUcum(v, "s");
3320      else if (s.equals("millisecond") || s.equals("milliseconds"))
3321        return Quantity.fromUcum(v, "ms");
3322      else
3323        return null;      
3324    } else {
3325      if (Utilities.isDecimal(s))
3326        return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1");
3327      else
3328        return null;
3329    }
3330  }
3331
3332
3333  private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3334    List<Base> result = new ArrayList<Base>();
3335    if (focus.size() != 1)
3336      result.add(new BooleanType(false).noExtensions());
3337    else if (focus.get(0) instanceof IntegerType)
3338      result.add(new BooleanType(true).noExtensions());
3339    else if (focus.get(0) instanceof BooleanType)
3340      result.add(new BooleanType(true).noExtensions());
3341    else if (focus.get(0) instanceof DecimalType)
3342      result.add(new BooleanType(true).noExtensions());
3343    else if (focus.get(0) instanceof StringType)
3344      result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)))).noExtensions());
3345    else 
3346      result.add(new BooleanType(false).noExtensions());
3347    return result;
3348  }
3349
3350  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3351    List<Base> result = new ArrayList<Base>();
3352    result.add(new IntegerType(focus.size()).noExtensions());
3353    return result;
3354  }
3355
3356  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3357    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3358    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
3359
3360    List<Base> result = new ArrayList<Base>();
3361    for (int i = i1; i < focus.size(); i++)
3362      result.add(focus.get(i));
3363    return result;
3364  }
3365
3366  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3367    List<Base> result = new ArrayList<Base>();
3368    for (int i = 1; i < focus.size(); i++)
3369      result.add(focus.get(i));
3370    return result;
3371  }
3372
3373  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3374    List<Base> result = new ArrayList<Base>();
3375    if (focus.size() > 0)
3376      result.add(focus.get(focus.size()-1));
3377    return result;
3378  }
3379
3380  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3381    List<Base> result = new ArrayList<Base>();
3382    if (focus.size() > 0)
3383      result.add(focus.get(0));
3384    return result;
3385  }
3386
3387
3388        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3389    List<Base> result = new ArrayList<Base>();
3390    List<Base> pc = new ArrayList<Base>();
3391    for (Base item : focus) {
3392      pc.clear();
3393      pc.add(item);
3394      if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)))
3395        result.add(item);
3396    }
3397    return result;
3398  }
3399
3400  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3401    List<Base> result = new ArrayList<Base>();
3402    List<Base> pc = new ArrayList<Base>();
3403    for (Base item : focus) {
3404      pc.clear();
3405      pc.add(item);
3406      result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
3407    }
3408    return result;
3409  }
3410
3411
3412        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3413    List<Base> result = new ArrayList<Base>();
3414    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3415    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size())
3416      result.add(focus.get(Integer.parseInt(s)));
3417    return result;
3418  }
3419
3420  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3421    List<Base> result = new ArrayList<Base>();
3422                result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions());
3423    return result;
3424  }
3425
3426  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3427    return makeBoolean(!convertToBoolean(focus));
3428  }
3429
3430  public class ElementDefinitionMatch {
3431    private ElementDefinition definition;
3432    private String fixedType;
3433    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
3434      super();
3435      this.definition = definition;
3436      this.fixedType = fixedType;
3437    }
3438    public ElementDefinition getDefinition() {
3439      return definition;
3440    }
3441    public String getFixedType() {
3442      return fixedType;
3443    }
3444
3445  }
3446
3447  private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException {
3448    if (Utilities.noString(type))
3449      throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName");
3450    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml"))
3451      return;
3452    if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 
3453      getSimpleTypeChildTypesByName(name, result);
3454    } else if (type.equals(TypeDetails.FP_ClassInfo)) { 
3455      getClassInfoChildTypesByName(name, result);
3456    } else {
3457    String url = null;
3458    if (type.contains("#")) {
3459      url = type.substring(0, type.indexOf("#"));
3460    } else {
3461      url = type;
3462    }
3463    String tail = "";
3464    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
3465    if (sd == null)
3466      throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong
3467    List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
3468    ElementDefinitionMatch m = null;
3469    if (type.contains("#"))
3470      m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false);
3471    if (m != null && hasDataType(m.definition)) {
3472      if (m.fixedType != null)
3473      {
3474        StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs()));
3475        if (dt == null)
3476          throw new DefinitionException("unknown data type "+m.fixedType);
3477        sdl.add(dt);
3478      } else
3479        for (TypeRefComponent t : m.definition.getType()) {
3480          StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs()));
3481          if (dt == null)
3482            throw new DefinitionException("unknown data type "+t.getCode());
3483          sdl.add(dt);
3484        }
3485    } else {
3486      sdl.add(sd);
3487      if (type.contains("#")) {
3488        tail = type.substring(type.indexOf("#")+1);
3489        tail = tail.substring(tail.indexOf("."));
3490      }
3491    }
3492
3493    for (StructureDefinition sdi : sdl) {
3494      String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
3495      if (name.equals("**")) {
3496        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
3497        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
3498          if (ed.getPath().startsWith(path))
3499            for (TypeRefComponent t : ed.getType()) {
3500              if (t.hasCode() && t.getCodeElement().hasValue()) {
3501                String tn = null;
3502                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
3503                  tn = sdi.getType()+"#"+ed.getPath();
3504                else
3505                  tn = t.getCode();
3506                if (t.getCode().equals("Resource")) {
3507                  for (String rn : worker.getResourceNames()) {
3508                    if (!result.hasType(worker, rn)) {
3509                      getChildTypesByName(result.addType(rn), "**", result);
3510                    }                  
3511                  }
3512                } else if (!result.hasType(worker, tn)) {
3513                  getChildTypesByName(result.addType(tn), "**", result);
3514                }
3515              }
3516            }
3517        }      
3518      } else if (name.equals("*")) {
3519        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
3520        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
3521          if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
3522            for (TypeRefComponent t : ed.getType()) {
3523              if (Utilities.noString(t.getCode())) // Element.id or Extension.url
3524                result.addType("System.string");
3525              else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
3526                result.addType(sdi.getType()+"#"+ed.getPath());
3527              else if (t.getCode().equals("Resource"))
3528                result.addTypes(worker.getResourceNames());
3529              else
3530                result.addType(t.getCode());
3531            }
3532        }
3533      } else {
3534        path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
3535
3536        ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
3537        if (ed != null) {
3538          if (!Utilities.noString(ed.getFixedType()))
3539            result.addType(ed.getFixedType());
3540          else
3541            for (TypeRefComponent t : ed.getDefinition().getType()) {
3542              if (Utilities.noString(t.getCode())) {
3543                if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url")) 
3544                  result.addType(TypeDetails.FP_NS, "string");
3545                break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
3546              }
3547
3548              ProfiledType pt = null;
3549              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
3550                pt = new ProfiledType(sdi.getUrl()+"#"+path);
3551              else if (t.getCode().equals("Resource"))
3552                result.addTypes(worker.getResourceNames());
3553              else
3554                pt = new ProfiledType(t.getCode());
3555              if (pt != null) {
3556                if (t.hasProfile())
3557                  pt.addProfiles(t.getProfile());
3558                if (ed.getDefinition().hasBinding())
3559                  pt.addBinding(ed.getDefinition().getBinding());
3560                result.addType(pt);
3561              }
3562            }
3563        }
3564      }
3565    }
3566    }
3567  }
3568
3569  private void getClassInfoChildTypesByName(String name, TypeDetails result) {
3570    if (name.equals("namespace"))
3571      result.addType(TypeDetails.FP_String);
3572    if (name.equals("name"))
3573      result.addType(TypeDetails.FP_String);
3574  }
3575
3576
3577  private void getSimpleTypeChildTypesByName(String name, TypeDetails result) {
3578    if (name.equals("namespace"))
3579      result.addType(TypeDetails.FP_String);
3580    if (name.equals("name"))
3581      result.addType(TypeDetails.FP_String);
3582  }
3583
3584
3585  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException {
3586    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3587      if (ed.getPath().equals(path)) {
3588        if (ed.hasContentReference()) {
3589          return getElementDefinitionById(sd, ed.getContentReference());
3590        } else
3591          return new ElementDefinitionMatch(ed, null);
3592      }
3593      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3)
3594        return new ElementDefinitionMatch(ed, null);
3595      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
3596        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
3597        if (primitiveTypes.contains(s))
3598          return new ElementDefinitionMatch(ed, s);
3599        else
3600        return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
3601      }
3602      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 
3603        // now we walk into the type.
3604        if (ed.getType().size() > 1)  // if there's more than one type, the test above would fail this
3605          throw new PathEngineException("Internal typing issue....");
3606        StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs()));
3607            if (nsd == null) 
3608              throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode());
3609        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName);
3610      }
3611      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
3612        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
3613        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName);
3614      }
3615    }
3616    return null;
3617  }
3618
3619  private boolean isAbstractType(List<TypeRefComponent> list) {
3620        return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
3621}
3622
3623
3624  private boolean hasType(ElementDefinition ed, String s) {
3625    for (TypeRefComponent t : ed.getType()) 
3626      if (s.equalsIgnoreCase(t.getCode()))
3627        return true;
3628    return false;
3629  }
3630
3631  private boolean hasDataType(ElementDefinition ed) {
3632    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
3633  }
3634
3635  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
3636    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3637      if (ref.equals("#"+ed.getId())) 
3638        return new ElementDefinitionMatch(ed, null);
3639    }
3640    return null;
3641  }
3642
3643
3644  public boolean hasLog() {
3645    return log != null && log.length() > 0;
3646  }
3647
3648
3649  public String takeLog() {
3650    if (!hasLog())
3651      return "";
3652    String s = log.toString();
3653    log = new StringBuilder();
3654    return s;
3655  }
3656
3657
3658  /** given an element definition in a profile, what element contains the differentiating fixed 
3659   * for the element, given the differentiating expresssion. The expression is only allowed to 
3660   * use a subset of FHIRPath
3661   * 
3662   * @param profile
3663   * @param element
3664   * @return
3665   * @throws PathEngineException 
3666   * @throws DefinitionException 
3667   */
3668  public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException {
3669    StructureDefinition sd = profile;
3670    ElementDefinition focus = null;
3671
3672    if (expr.getKind() == Kind.Name) {
3673      List<ElementDefinition> childDefinitions;
3674      childDefinitions = ProfileUtilities.getChildMap(sd, element);
3675      // if that's empty, get the children of the type
3676      if (childDefinitions.isEmpty()) {
3677        sd = fetchStructureByType(element);
3678        if (sd == null)
3679          throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getProfile()+"' on "+element.getId()+" could not be resolved");
3680        childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
3681      }
3682      for (ElementDefinition t : childDefinitions) {
3683        if (tailMatches(t, expr.getName())) {
3684          focus = t;
3685          break;
3686        }
3687      }
3688    } else if (expr.getKind() == Kind.Function) {
3689      if ("resolve".equals(expr.getName())) {
3690        if (!element.hasType())
3691          throw new DefinitionException("illegal use of resolve() in discriminator - no type on element "+element.getId());
3692        if (element.getType().size() > 1)
3693          throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on "+element.getId());
3694        if (!element.getType().get(0).hasTarget())
3695          throw new DefinitionException("illegal use of resolve() in discriminator - type on "+element.getId()+" is not Reference ("+element.getType().get(0).getCode()+")");
3696        if (element.getType().get(0).getTargetProfile().size() > 1)
3697          throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on "+element.getId());
3698        sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue());
3699        if (sd == null)
3700          throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getTargetProfile()+"' on "+element.getId()+" could not be resolved");
3701        focus = sd.getSnapshot().getElementFirstRep();
3702      } else if ("extension".equals(expr.getName())) {
3703        String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue();
3704//        targetUrl = targetUrl.substring(1,targetUrl.length()-1);
3705        List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(sd, element);
3706        for (ElementDefinition t : childDefinitions) {
3707          if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
3708           sd = worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue());
3709           while (sd!=null && !sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension"))
3710             sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3711           if (sd.getUrl().equals(targetUrl)) {
3712             focus = t;
3713             break;
3714           }
3715          }
3716        }
3717      } else 
3718        throw new DefinitionException("illegal function name "+expr.getName()+"() in discriminator");
3719    } else if (expr.getKind() == Kind.Group) {
3720      throw new DefinitionException("illegal expression syntax in discriminator (group)");
3721    } else if (expr.getKind() == Kind.Constant) {
3722      throw new DefinitionException("illegal expression syntax in discriminator (const)");
3723    }
3724
3725    if (focus == null)
3726      throw new DefinitionException("Unable to resolve discriminator");      
3727    else if (expr.getInner() == null)
3728      return focus;
3729    else
3730      return evaluateDefinition(expr.getInner(), sd, focus);
3731  }
3732
3733  private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException {
3734    if (ed.getType().size() == 0)
3735      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, no type");
3736    if (ed.getType().size() > 1)
3737      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple types");
3738    if (ed.getType().get(0).getProfile().size() > 1)
3739      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple type profiles");
3740    if (ed.hasSlicing()) 
3741      throw new DefinitionException("Error in discriminator at "+ed.getId()+": slicing found");
3742    if (ed.getType().get(0).hasProfile()) 
3743      return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue());
3744    else
3745      return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs()));
3746  }
3747
3748
3749  private boolean tailMatches(ElementDefinition t, String d) {
3750    String tail = tailDot(t.getPath());
3751    if (d.contains("["))
3752      return tail.startsWith(d.substring(0, d.indexOf('[')));
3753    else if (tail.equals(d))
3754      return true;
3755    else if (t.getType().size()==1 && t.getPath().toUpperCase().endsWith(t.getType().get(0).getCode().toUpperCase()))
3756      return tail.startsWith(d);
3757    
3758    return false;
3759  }
3760
3761  private String tailDot(String path) {
3762    return path.substring(path.lastIndexOf(".") + 1);
3763  }
3764
3765}