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