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