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