001package org.hl7.fhir.r5.utils;
002
003import java.math.BigDecimal;
004import java.math.RoundingMode;
005import java.rmi.server.LoaderHandler;
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.Base64;
009import java.util.Calendar;
010import java.util.Date;
011import java.util.EnumSet;
012import java.util.HashMap;
013import java.util.HashSet;
014import java.util.List;
015import java.util.Map;
016import java.util.Set;
017
018import org.apache.commons.lang3.NotImplementedException;
019import org.fhir.ucum.Decimal;
020import org.fhir.ucum.Pair;
021import org.fhir.ucum.UcumException;
022import org.hl7.fhir.exceptions.DefinitionException;
023import org.hl7.fhir.exceptions.FHIRException;
024import org.hl7.fhir.exceptions.FHIRFormatError;
025import org.hl7.fhir.exceptions.PathEngineException;
026import org.hl7.fhir.r5.conformance.ProfileUtilities;
027import org.hl7.fhir.r5.context.IWorkerContext;
028import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
029import org.hl7.fhir.r5.model.Base;
030import org.hl7.fhir.r5.model.BaseDateTimeType;
031import org.hl7.fhir.r5.model.BooleanType;
032import org.hl7.fhir.r5.model.CodeableConcept;
033import org.hl7.fhir.r5.model.Constants;
034import org.hl7.fhir.r5.model.DateTimeType;
035import org.hl7.fhir.r5.model.DateType;
036import org.hl7.fhir.r5.model.DecimalType;
037import org.hl7.fhir.r5.model.Element;
038import org.hl7.fhir.r5.model.ElementDefinition;
039import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
040import org.hl7.fhir.r5.model.ExpressionNode;
041import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
042import org.hl7.fhir.r5.model.ExpressionNode.Function;
043import org.hl7.fhir.r5.model.ExpressionNode.Kind;
044import org.hl7.fhir.r5.model.ExpressionNode.Operation;
045import org.hl7.fhir.r5.model.Property.PropertyMatcher;
046import org.hl7.fhir.r5.model.IntegerType;
047import org.hl7.fhir.r5.model.Property;
048import org.hl7.fhir.r5.model.Quantity;
049import org.hl7.fhir.r5.model.Resource;
050import org.hl7.fhir.r5.model.StringType;
051import org.hl7.fhir.r5.model.StructureDefinition;
052import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
053import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
054import org.hl7.fhir.r5.model.TimeType;
055import org.hl7.fhir.r5.model.TypeConvertor;
056import org.hl7.fhir.r5.model.TypeDetails;
057import org.hl7.fhir.r5.model.TypeDetails.ProfiledType;
058import org.hl7.fhir.r5.model.ValueSet;
059import org.hl7.fhir.r5.renderers.DataRenderer;
060import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
061import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
062import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
063import org.hl7.fhir.utilities.MergedList;
064import org.hl7.fhir.utilities.MergedList.MergeNode;
065import org.hl7.fhir.utilities.SourceLocation;
066import org.hl7.fhir.utilities.Utilities;
067import org.hl7.fhir.utilities.i18n.I18nConstants;
068import org.hl7.fhir.utilities.validation.ValidationMessage;
069import org.hl7.fhir.utilities.validation.ValidationOptions;
070import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
071import org.hl7.fhir.utilities.xhtml.NodeType;
072import org.hl7.fhir.utilities.xhtml.XhtmlNode;
073
074import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
075import ca.uhn.fhir.util.ElementUtil;
076
077/*
078  Copyright (c) 2011+, HL7, Inc.
079  All rights reserved.
080  
081  Redistribution and use in source and binary forms, with or without modification, 
082  are permitted provided that the following conditions are met:
083    
084   * Redistributions of source code must retain the above copyright notice, this 
085     list of conditions and the following disclaimer.
086   * Redistributions in binary form must reproduce the above copyright notice, 
087     this list of conditions and the following disclaimer in the documentation 
088     and/or other materials provided with the distribution.
089   * Neither the name of HL7 nor the names of its contributors may be used to 
090     endorse or promote products derived from this software without specific 
091     prior written permission.
092  
093  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
094  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
095  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
096  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
097  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
098  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
099  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
100  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
101  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
102  POSSIBILITY OF SUCH DAMAGE.
103  
104 */
105
106
107/**
108 * 
109 * @author Grahame Grieve
110 *
111 */
112public class FHIRPathEngine {
113  
114  private enum Equality { Null, True, False }
115
116  private class FHIRConstant extends Base {
117
118    private static final long serialVersionUID = -8933773658248269439L;
119    private String value;
120
121    public FHIRConstant(String value) {
122      this.value = value;
123    }
124
125    @Override
126    public String fhirType() {
127      return "%constant";
128    }
129
130    @Override
131    protected void listChildren(List<Property> result) {
132    }
133
134    @Override
135    public String getIdBase() {
136      return null;
137    }
138
139    @Override
140    public void setIdBase(String value) {
141    }
142
143    public String getValue() {
144      return value;
145    }
146    
147    @Override
148    public String primitiveValue() {
149      return value;
150    }
151  }
152  
153  private class ClassTypeInfo extends Base {
154    private static final long serialVersionUID = 4909223114071029317L;
155    private Base instance;
156
157    public ClassTypeInfo(Base instance) {
158      super();
159      this.instance = instance;
160    }
161
162    @Override
163    public String fhirType() {
164      return "ClassInfo";
165    }
166
167    @Override
168    protected void listChildren(List<Property> result) {
169    }
170
171    @Override
172    public String getIdBase() {
173      return null;
174    }
175
176    @Override
177    public void setIdBase(String value) {
178    }
179    
180    public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
181      if (name.equals("name")) {
182        return new Base[]{new StringType(getName())};
183      } else if (name.equals("namespace")) { 
184        return new Base[]{new StringType(getNamespace())};
185      } else {
186        return super.getProperty(hash, name, checkValid);
187      }
188    }
189
190    private String getNamespace() {
191      if ((instance instanceof Resource)) {
192        return "FHIR";
193      } else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) {
194        return "System";
195      } else {
196        return "FHIR";
197      }
198    }
199
200    private String getName() {
201      if ((instance instanceof Resource)) {
202        return instance.fhirType();
203      } else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions()) {
204        return Utilities.capitalize(instance.fhirType());
205      } else {
206        return instance.fhirType();
207      }
208    }
209  }
210
211  public static class TypedElementDefinition {
212    private ElementDefinition element;
213    private String type;
214    public TypedElementDefinition(ElementDefinition element, String type) {
215      super();
216      this.element = element;
217      this.type = type;
218    }
219    public TypedElementDefinition(ElementDefinition element) {
220      super();
221      this.element = element;
222    }
223    public ElementDefinition getElement() {
224      return element;
225    }
226    public String getType() {
227      return type;
228    }
229    public List<TypeRefComponent> getTypes() {
230      List<TypeRefComponent> res = new ArrayList<ElementDefinition.TypeRefComponent>();
231      for (TypeRefComponent tr : element.getType()) {
232        if (type == null || type.equals(tr.getCode())) {
233          res.add(tr);
234        }
235      }
236      return res;
237    }
238  }
239  private IWorkerContext worker;
240  private IEvaluationContext hostServices;
241  private StringBuilder log = new StringBuilder();
242  private Set<String> primitiveTypes = new HashSet<String>();
243  private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
244  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  
245  private ValidationOptions terminologyServiceOptions = new ValidationOptions();
246  private ProfileUtilities profileUtilities;
247  private String location; // for error messages
248  private boolean allowPolymorphicNames;
249
250  // if the fhir path expressions are allowed to use constants beyond those defined in the specification
251  // the application can implement them by providing a constant resolver 
252  public interface IEvaluationContext {
253    public class FunctionDetails {
254      private String description;
255      private int minParameters;
256      private int maxParameters;
257      public FunctionDetails(String description, int minParameters, int maxParameters) {
258        super();
259        this.description = description;
260        this.minParameters = minParameters;
261        this.maxParameters = maxParameters;
262      }
263      public String getDescription() {
264        return description;
265      }
266      public int getMinParameters() {
267        return minParameters;
268      }
269      public int getMaxParameters() {
270        return maxParameters;
271      }
272
273    }
274
275    /**
276     * A constant reference - e.g. a reference to a name that must be resolved in context.
277     * The % will be removed from the constant name before this is invoked.
278     * 
279     * This will also be called if the host invokes the FluentPath engine with a context of null
280     *  
281     * @param appContext - content passed into the fluent path engine
282     * @param name - name reference to resolve
283     * @param beforeContext - whether this is being called before the name is resolved locally, or not
284     * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
285     */
286    public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext)  throws PathEngineException;
287    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException;
288    
289    /**
290     * when the .log() function is called
291     * 
292     * @param argument
293     * @param focus
294     * @return
295     */
296    public boolean log(String argument, List<Base> focus);
297
298    // extensibility for functions
299    /**
300     * 
301     * @param functionName
302     * @return null if the function is not known
303     */
304    public FunctionDetails resolveFunction(String functionName);
305    
306    /**
307     * Check the function parameters, and throw an error if they are incorrect, or return the type for the function
308     * @param functionName
309     * @param parameters
310     * @return
311     */
312    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException;
313    
314    /**
315     * @param appContext
316     * @param functionName
317     * @param parameters
318     * @return
319     */
320    public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters);
321    
322    /**
323     * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
324     * @appContext - passed in by the host to the FHIRPathEngine
325     * @param url the reference (Reference.reference or the value of the canonical
326     * @return
327     * @throws FHIRException 
328     */
329    public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException;
330    
331    public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException;
332    
333    /* 
334     * return the value set referenced by the url, which has been used in memberOf()
335     */
336    public ValueSet resolveValueSet(Object appContext, String url);
337  }
338
339  /**
340   * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
341   */
342  public FHIRPathEngine(IWorkerContext worker) {
343    this(worker, new ProfileUtilities(worker, null, null));
344  }
345
346  public FHIRPathEngine(IWorkerContext worker, ProfileUtilities utilities) {
347    super();
348    this.worker = worker;
349    profileUtilities = utilities; 
350    for (StructureDefinition sd : worker.getStructures()) {
351      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) {
352        allTypes.put(sd.getName(), sd);
353      }
354      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { 
355        primitiveTypes.add(sd.getName());
356      }
357    }
358  }
359
360
361  // --- 3 methods to override in children -------------------------------------------------------
362  // if you don't override, it falls through to the using the base reference implementation 
363  // HAPI overrides to these to support extending the base model
364
365  public IEvaluationContext getHostServices() {
366    return hostServices;
367  }
368
369
370  public void setHostServices(IEvaluationContext constantResolver) {
371    this.hostServices = constantResolver;
372  }
373
374  public String getLocation() {
375    return location;
376  }
377
378
379  public void setLocation(String location) {
380    this.location = location;
381  }
382
383
384  /**
385   * Given an item, return all the children that conform to the pattern described in name
386   * 
387   * Possible patterns:
388   *  - a simple name (which may be the base of a name with [] e.g. value[x])
389   *  - a name with a type replacement e.g. valueCodeableConcept
390   *  - * which means all children
391   *  - ** which means all descendants
392   *  
393   * @param item
394   * @param name
395   * @param result
396         * @throws FHIRException 
397         */
398  protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
399    String tn = null;
400    if (isAllowPolymorphicNames()) {
401      // we'll look to see whether we hav a polymorphic name 
402      for (Property p : item.children()) {
403        if (p.getName().endsWith("[x]")) {
404          String n = p.getName().substring(0, p.getName().length()-3);
405          if (name.startsWith(n)) {
406            tn = name.substring(n.length());
407            name = n;
408            break;            
409          }
410        }
411      }
412    }
413    Base[] list = item.listChildrenByName(name, false);
414    if (list != null) {
415      for (Base v : list) {
416        if (v != null && (tn == null || v.fhirType().equalsIgnoreCase(tn))) {
417          result.add(v);
418        }
419      }
420    }
421  }
422
423  
424  public boolean isLegacyMode() {
425    return legacyMode;
426  }
427
428
429  public void setLegacyMode(boolean legacyMode) {
430    this.legacyMode = legacyMode;
431  }
432
433
434  // --- public API -------------------------------------------------------
435  /**
436   * Parse a path for later use using execute
437   * 
438   * @param path
439   * @return
440   * @throws PathEngineException 
441   * @throws Exception
442   */
443  public ExpressionNode parse(String path) throws FHIRLexerException {
444    return parse(path, null);
445  }
446  
447  public ExpressionNode parse(String path, String name) throws FHIRLexerException {
448    FHIRLexer lexer = new FHIRLexer(path, name);
449    if (lexer.done()) {
450      throw lexer.error("Path cannot be empty");
451    }
452    ExpressionNode result = parseExpression(lexer, true);
453    if (!lexer.done()) {
454      throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\"");
455    }
456    result.check();
457    return result;    
458  }
459
460  public static class ExpressionNodeWithOffset {
461    private int offset;
462    private ExpressionNode node;
463    public ExpressionNodeWithOffset(int offset, ExpressionNode node) {
464      super();
465      this.offset = offset;
466      this.node = node;
467    }
468    public int getOffset() {
469      return offset;
470    }
471    public ExpressionNode getNode() {
472      return node;
473    }
474    
475  }
476  /**
477   * Parse a path for later use using execute
478   * 
479   * @param path
480   * @return
481   * @throws PathEngineException 
482   * @throws Exception
483   */
484  public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException {
485    FHIRLexer lexer = new FHIRLexer(path, i);
486    if (lexer.done()) {
487      throw lexer.error("Path cannot be empty");
488    }
489    ExpressionNode result = parseExpression(lexer, true);
490    result.check();
491    return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result);    
492  }
493
494  /**
495   * Parse a path that is part of some other syntax
496   *  
497   * @return
498   * @throws PathEngineException 
499   * @throws Exception
500   */
501  public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException {
502    ExpressionNode result = parseExpression(lexer, true);
503    result.check();
504    return result;    
505  }
506
507  /**
508   * check that paths referred to in the ExpressionNode are valid
509   * 
510   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
511   * 
512   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
513   * 
514   * @param context - the logical type against which this path is applied
515   * @throws DefinitionException
516   * @throws PathEngineException 
517   * @if the path is not valid
518   */
519  public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
520    // if context is a path that refers to a type, do that conversion now 
521    TypeDetails types; 
522    if (context == null) {
523      types = null; // this is a special case; the first path reference will have to resolve to something in the context
524    } else if (!context.contains(".")) {
525      StructureDefinition sd = worker.fetchTypeDefinition(context);
526      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
527    } else {
528      String ctxt = context.substring(0, context.indexOf('.'));
529      if (Utilities.isAbsoluteUrl(resourceType)) {
530        ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt;
531      }
532      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt);
533      if (sd == null) {
534        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, context);
535      }
536      ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr);
537      if (ed == null) {
538        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context);
539      }
540      if (ed.fixedType != null) { 
541        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
542      } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 
543        types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context);
544      } else {
545        types = new TypeDetails(CollectionStatus.SINGLETON);
546        for (TypeRefComponent t : ed.getDefinition().getType()) { 
547          types.addType(t.getCode());
548        }
549      }
550    }
551
552    return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true);
553  }
554
555  private FHIRException makeException(ExpressionNode holder, String constName, Object... args) {
556    String fmt = worker.formatMessage(constName, args);
557    if (location != null) {
558      fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location);
559    }
560    if (holder != null) {      
561      return new PathEngineException(fmt, holder.getStart(), holder.toString());
562    } else {
563      return new PathEngineException(fmt);
564    }
565  }
566
567  public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
568    // if context is a path that refers to a type, do that conversion now 
569    TypeDetails types; 
570    if (!context.contains(".")) {
571      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
572    } else {
573      ElementDefinitionMatch ed = getElementDefinition(sd, context, true, expr);
574      if (ed == null) {
575        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, context);
576      }
577      if (ed.fixedType != null) { 
578        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
579      } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { 
580        types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context);
581      } else {
582        types = new TypeDetails(CollectionStatus.SINGLETON);
583        for (TypeRefComponent t : ed.getDefinition().getType()) { 
584          types.addType(t.getCode());
585        }
586      }
587    }
588
589    return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true);
590  }
591
592  public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
593    // if context is a path that refers to a type, do that conversion now 
594    TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
595    return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true);
596  }
597
598  public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
599    return check(appContext, resourceType, context, parse(expr));
600  }
601
602  private Integer compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
603    DateTimeType left = theL instanceof DateTimeType ? (DateTimeType) theL : new DateTimeType(theL.primitiveValue()); 
604    DateTimeType right = theR instanceof DateTimeType ? (DateTimeType) theR : new DateTimeType(theR.primitiveValue()); 
605
606    if (theEquivalenceTest) {
607      return left.equalsUsingFhirPathRules(right) == Boolean.TRUE ? 0 : 1;
608    }
609
610    if (left.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
611      left.setTimeZoneZulu(true);
612    }
613    if (right.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
614      right.setTimeZoneZulu(true);
615    }
616    return BaseDateTimeType.compareTimes(left, right, null);
617  }
618
619  private Integer compareTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
620    TimeType left = theL instanceof TimeType ? (TimeType) theL : new TimeType(theL.primitiveValue()); 
621    TimeType right = theR instanceof TimeType ? (TimeType) theR : new TimeType(theR.primitiveValue()); 
622
623    if (left.getHour() < right.getHour()) {
624      return -1;
625    } else if (left.getHour() > right.getHour()) {
626      return 1;
627      // hour is not a valid precision 
628//    } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.YEAR && dateRight.getPrecision() == TemporalPrecisionEnum.YEAR) {
629//      return 0;
630//    } else if (dateLeft.getPrecision() == TemporalPrecisionEnum.HOUR || dateRight.getPrecision() == TemporalPrecisionEnum.HOUR) {
631//      return null;
632    }
633
634    if (left.getMinute() < right.getMinute()) {
635      return -1;
636    } else if (left.getMinute() > right.getMinute()) {
637      return 1;
638    } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE && right.getPrecision() == TemporalPrecisionEnum.MINUTE) {
639      return 0;
640    } else if (left.getPrecision() == TemporalPrecisionEnum.MINUTE || right.getPrecision() == TemporalPrecisionEnum.MINUTE) {
641      return null;
642    }
643
644    if (left.getSecond() < right.getSecond()) {
645      return -1;
646    } else if (left.getSecond() > right.getSecond()) {
647      return 1;
648    } else {
649      return 0;
650    }
651
652  }
653
654
655  /**
656   * evaluate a path and return the matching elements
657   * 
658   * @param base - the object against which the path is being evaluated
659   * @param ExpressionNode - the parsed ExpressionNode statement to use
660   * @return
661   * @throws FHIRException 
662   * @
663   */
664        public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException {
665    List<Base> list = new ArrayList<Base>();
666    if (base != null) {
667      list.add(base);
668    }
669    log = new StringBuilder();
670    return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true);
671  }
672
673  /**
674   * evaluate a path and return the matching elements
675   * 
676   * @param base - the object against which the path is being evaluated
677   * @param path - the FHIR Path statement to use
678   * @return
679         * @throws FHIRException 
680   * @
681   */
682        public List<Base> evaluate(Base base, String path) throws FHIRException {
683    ExpressionNode exp = parse(path);
684    List<Base> list = new ArrayList<Base>();
685    if (base != null) {
686      list.add(base);
687    }
688    log = new StringBuilder();
689    return execute(new ExecutionContext(null, base.isResource() ? base : null, base.isResource() ? base : null, base, null, base), list, exp, true);
690  }
691
692  /**
693   * evaluate a path and return the matching elements
694   * 
695   * @param base - the object against which the path is being evaluated
696   * @param ExpressionNode - the parsed ExpressionNode statement to use
697   * @return
698         * @throws FHIRException 
699   * @
700   */
701        public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
702    List<Base> list = new ArrayList<Base>();
703    if (base != null) {
704      list.add(base);
705    }
706    log = new StringBuilder();
707    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, ExpressionNode, true);
708  }
709
710  /**
711   * evaluate a path and return the matching elements
712   * 
713   * @param base - the object against which the path is being evaluated
714   * @param expressionNode - the parsed ExpressionNode statement to use
715   * @return
716   * @throws FHIRException 
717   * @
718   */
719  public List<Base> evaluate(Object appContext, Base focusResource, Base rootResource, Base base, ExpressionNode expressionNode) throws FHIRException {
720    List<Base> list = new ArrayList<Base>();
721    if (base != null) {
722      list.add(base);
723    }
724    log = new StringBuilder();
725    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, expressionNode, true);
726  }
727
728  /**
729   * evaluate a path and return the matching elements
730   * 
731   * @param base - the object against which the path is being evaluated
732   * @param path - the FHIR Path statement to use
733   * @return
734         * @throws FHIRException 
735   * @
736   */
737        public List<Base> evaluate(Object appContext, Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException {
738    ExpressionNode exp = parse(path);
739    List<Base> list = new ArrayList<Base>();
740    if (base != null) {
741      list.add(base);
742    }
743    log = new StringBuilder();
744    return execute(new ExecutionContext(appContext, focusResource, rootResource, base, null, base), list, exp, true);
745  }
746
747  /**
748   * evaluate a path and return true or false (e.g. for an invariant)
749   * 
750   * @param base - the object against which the path is being evaluated
751   * @param path - the FHIR Path statement to use
752   * @return
753         * @throws FHIRException 
754   * @
755   */
756        public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, String path) throws FHIRException {
757    return convertToBoolean(evaluate(null, focusResource, rootResource, base, path));
758  }
759
760  /**
761   * evaluate a path and return true or false (e.g. for an invariant)
762   * 
763   * @param base - the object against which the path is being evaluated
764   * @return
765   * @throws FHIRException 
766   * @
767   */
768  public boolean evaluateToBoolean(Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException {
769    return convertToBoolean(evaluate(null, focusResource, rootResource, base, node));
770  }
771
772  /**
773   * evaluate a path and return true or false (e.g. for an invariant)
774   * 
775   * @param appInfo - application context
776   * @param base - the object against which the path is being evaluated
777   * @return
778   * @throws FHIRException 
779   * @
780   */
781  public boolean evaluateToBoolean(Object appInfo, Resource focusResource, Resource rootResource, Base base, ExpressionNode node) throws FHIRException {
782    return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node));
783  }
784
785  /**
786   * evaluate a path and return true or false (e.g. for an invariant)
787   * 
788   * @param base - the object against which the path is being evaluated
789   * @return
790   * @throws FHIRException 
791   * @
792   */
793  public boolean evaluateToBoolean(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException {
794    return convertToBoolean(evaluate(appInfo, focusResource, rootResource, base, node));
795  }
796
797  /**
798   * evaluate a path and a string containing the outcome (for display)
799   * 
800   * @param base - the object against which the path is being evaluated
801   * @param path - the FHIR Path statement to use
802   * @return
803         * @throws FHIRException 
804   * @
805   */
806  public String evaluateToString(Base base, String path) throws FHIRException {
807    return convertToString(evaluate(base, path));
808  }
809
810  public String evaluateToString(Object appInfo, Base focusResource, Base rootResource, Base base, ExpressionNode node) throws FHIRException {
811    return convertToString(evaluate(appInfo, focusResource, rootResource, base, node));
812  }
813
814  /**
815   * worker routine for converting a set of objects to a string representation
816   * 
817   * @param items - result from @evaluate
818   * @return
819   */
820  public String convertToString(List<Base> items) {
821    StringBuilder b = new StringBuilder();
822    boolean first = true;
823    for (Base item : items) {
824      if (first)  {
825        first = false;
826      } else {
827        b.append(',');
828      }
829
830      b.append(convertToString(item));
831    }
832    return b.toString();
833  }
834
835  public String convertToString(Base item) {
836    if (item.isPrimitive()) {
837      return item.primitiveValue();
838    } else if (item instanceof Quantity) {
839      Quantity q = (Quantity) item;
840      if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")
841          && (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) {
842        return q.getValue().toPlainString()+" "+q.getUnit();
843      }
844      if (q.getSystem().equals("http://unitsofmeasure.org")) {
845        String u = "'"+q.getCode()+"'";
846        return q.getValue().toPlainString()+" "+u;
847      } else {
848        return item.toString();
849      }
850    } else
851      return item.toString();
852  }
853
854  /**
855   * worker routine for converting a set of objects to a boolean representation (for invariants)
856   * 
857   * @param items - result from @evaluate
858   * @return
859   */
860  public boolean convertToBoolean(List<Base> items) {
861    if (items == null) {
862      return false;
863    } else if (items.size() == 1 && items.get(0) instanceof BooleanType) {
864      return ((BooleanType) items.get(0)).getValue();
865    } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) { // element model
866      return Boolean.valueOf(items.get(0).primitiveValue());
867    } else { 
868      return items.size() > 0;
869    }
870  }
871
872
873  private void log(String name, List<Base> contents) {
874    if (hostServices == null || !hostServices.log(name, contents)) {
875      if (log.length() > 0) {
876        log.append("; ");
877      }
878      log.append(name);
879      log.append(": ");
880      boolean first = true;
881      for (Base b : contents) {
882        if (first) {
883          first = false;
884        } else {
885          log.append(",");
886        }
887        log.append(convertToString(b));
888      }
889    }
890  }
891
892  public String forLog() {
893    if (log.length() > 0) {
894      return " ("+log.toString()+")";
895    } else {
896      return "";
897    }
898  }
899
900  private class ExecutionContext {
901    private Object appInfo;
902    private Base focusResource;
903    private Base rootResource;
904    private Base context;
905    private Base thisItem;
906    private List<Base> total;
907    private Map<String, Base> aliases;
908    private int index;
909    
910    public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map<String, Base> aliases, Base thisItem) {
911      this.appInfo = appInfo;
912      this.context = context;
913      this.focusResource = resource; 
914      this.rootResource = rootResource; 
915      this.aliases = aliases;
916      this.thisItem = thisItem;
917      this.index = 0;
918    }
919    public Base getFocusResource() {
920      return focusResource;
921    }
922    public Base getRootResource() {
923        return rootResource;
924    }
925    public Base getThisItem() {
926      return thisItem;
927    }
928    public List<Base> getTotal() {
929      return total;
930    }
931    
932    public void next() {
933      index++;
934    }
935    public Base getIndex() {
936      return new IntegerType(index);
937    }
938    
939    public void addAlias(String name, List<Base> focus) throws FHIRException {
940      if (aliases == null) {
941        aliases = new HashMap<String, Base>();
942      } else {
943        aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change
944      }
945      if (focus.size() > 1) {
946        throw makeException(null, I18nConstants.FHIRPATH_ALIAS_COLLECTION);
947      }
948      aliases.put(name, focus.size() == 0 ? null : focus.get(0));      
949    }
950    public Base getAlias(String name) {
951      return aliases == null ? null : aliases.get(name);
952    }
953    public ExecutionContext setIndex(int i) {
954      index = i;
955      return this;
956    }
957  }
958
959  private class ExecutionTypeContext {
960    private Object appInfo; 
961    private String resource;
962    private TypeDetails context;
963    private TypeDetails thisItem;
964    private TypeDetails total;
965
966
967    public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) {
968      super();
969      this.appInfo = appInfo;
970      this.resource = resource;
971      this.context = context;
972      this.thisItem = thisItem;
973      
974    }
975    public String getResource() {
976      return resource;
977    }
978    public TypeDetails getThisItem() {
979      return thisItem;
980    }
981
982    
983  }
984
985  private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
986    ExpressionNode result = new ExpressionNode(lexer.nextId());
987    ExpressionNode wrapper = null;
988    SourceLocation c = lexer.getCurrentStartLocation();
989    result.setStart(lexer.getCurrentLocation());
990    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
991    // so we back correct for both +/- and as part of a numeric constant below.
992    
993    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
994    // so we back correct for both +/- and as part of a numeric constant below.
995    if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) {
996      wrapper = new ExpressionNode(lexer.nextId());
997      wrapper.setKind(Kind.Unary);
998      wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take()));
999      wrapper.setStart(lexer.getCurrentLocation());
1000      wrapper.setProximal(proximal);
1001    }
1002
1003    if (lexer.getCurrent() == null) {
1004      throw lexer.error("Expression terminated unexpectedly");
1005    } else if (lexer.isConstant()) {
1006      boolean isString = lexer.isStringConstant();
1007      if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) {
1008        // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations
1009        wrapper = new ExpressionNode(lexer.nextId());
1010        wrapper.setKind(Kind.Unary);
1011        wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1)));
1012        wrapper.setProximal(proximal);
1013        wrapper.setStart(lexer.getCurrentLocation());
1014        lexer.setCurrent(lexer.getCurrent().substring(1));
1015      }
1016      result.setConstant(processConstant(lexer));
1017      result.setKind(Kind.Constant);
1018      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"))) {
1019        // it's a quantity
1020        String ucum = null;
1021        String unit = null;
1022        if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) {
1023          String s = lexer.take();
1024          unit = s;
1025          if (s.equals("year") || s.equals("years")) {
1026            // this is not the UCUM year
1027          } else if (s.equals("month") || s.equals("months")) {
1028            // this is not the UCUM month
1029          } else if (s.equals("week") || s.equals("weeks")) {
1030            ucum = "wk";
1031          } else if (s.equals("day") || s.equals("days")) {
1032            ucum = "d";
1033          } else if (s.equals("hour") || s.equals("hours")) {
1034            ucum = "h";
1035          } else if (s.equals("minute") || s.equals("minutes")) {
1036            ucum = "min";
1037          } else if (s.equals("second") || s.equals("seconds")) {
1038            ucum = "s";
1039          } else { // (s.equals("millisecond") || s.equals("milliseconds"))
1040            ucum = "ms";
1041          } 
1042        } else {
1043          ucum = lexer.readConstant("units");
1044        }
1045        result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum));
1046      }
1047      result.setEnd(lexer.getCurrentLocation());
1048    } else if ("(".equals(lexer.getCurrent())) {
1049      lexer.next();
1050      result.setKind(Kind.Group);
1051      result.setGroup(parseExpression(lexer, true));
1052      if (!")".equals(lexer.getCurrent())) {
1053        throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\"");
1054      }
1055      result.setEnd(lexer.getCurrentLocation());
1056      lexer.next();
1057    } else {
1058      if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) {
1059        throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name");
1060      }
1061      if (lexer.isFixedName()) {
1062        result.setName(lexer.readFixedName("Path Name"));
1063      } else {
1064        result.setName(lexer.take());
1065      }
1066      result.setEnd(lexer.getCurrentLocation());
1067      if (!result.checkName()) {
1068        throw lexer.error("Found "+result.getName()+" expecting a valid token name");
1069      }
1070      if ("(".equals(lexer.getCurrent())) {
1071        Function f = Function.fromCode(result.getName());
1072        FunctionDetails details = null;
1073        if (f == null) {
1074          if (hostServices != null) {
1075            details = hostServices.resolveFunction(result.getName());
1076          }
1077          if (details == null) {
1078            throw lexer.error("The name "+result.getName()+" is not a valid function name");
1079          }
1080          f = Function.Custom;
1081        }
1082        result.setKind(Kind.Function);
1083        result.setFunction(f);
1084        lexer.next();
1085        while (!")".equals(lexer.getCurrent())) { 
1086          result.getParameters().add(parseExpression(lexer, true));
1087          if (",".equals(lexer.getCurrent())) {
1088            lexer.next();
1089          } else if (!")".equals(lexer.getCurrent())) {
1090            throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected");
1091          }
1092        }
1093        result.setEnd(lexer.getCurrentLocation());
1094        lexer.next();
1095        checkParameters(lexer, c, result, details);
1096      } else {
1097        result.setKind(Kind.Name);
1098      }
1099    }
1100    ExpressionNode focus = result;
1101    if ("[".equals(lexer.getCurrent())) {
1102      lexer.next();
1103      ExpressionNode item = new ExpressionNode(lexer.nextId());
1104      item.setKind(Kind.Function);
1105      item.setFunction(ExpressionNode.Function.Item);
1106      item.getParameters().add(parseExpression(lexer, true));
1107      if (!lexer.getCurrent().equals("]")) {
1108        throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected");
1109      }
1110      lexer.next();
1111      result.setInner(item);
1112      focus = item;
1113    }
1114    if (".".equals(lexer.getCurrent())) {
1115      lexer.next();
1116      focus.setInner(parseExpression(lexer, false));
1117    }
1118    result.setProximal(proximal);
1119    if (proximal) {
1120      while (lexer.isOp()) {
1121        focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent()));
1122        focus.setOpStart(lexer.getCurrentStartLocation());
1123        focus.setOpEnd(lexer.getCurrentLocation());
1124        lexer.next();
1125        focus.setOpNext(parseExpression(lexer, false));
1126        focus = focus.getOpNext();
1127      }
1128      result = organisePrecedence(lexer, result);
1129    }
1130    if (wrapper != null) {
1131      wrapper.setOpNext(result);
1132      result.setProximal(false);
1133      result = wrapper;
1134    }
1135    return result;
1136  }
1137
1138  private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) {
1139    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 
1140    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 
1141    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 
1142    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual));
1143    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is));
1144    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
1145    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
1146    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
1147    // last: implies
1148    return node;
1149  }
1150
1151  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
1152    //    work : boolean;
1153    //    focus, node, group : ExpressionNode;
1154
1155    assert(start.isProximal());
1156
1157    // is there anything to do?
1158    boolean work = false;
1159    ExpressionNode focus = start.getOpNext();
1160    if (ops.contains(start.getOperation())) {
1161      while (focus != null && focus.getOperation() != null) {
1162        work = work || !ops.contains(focus.getOperation());
1163        focus = focus.getOpNext();
1164      }
1165    } else {
1166      while (focus != null && focus.getOperation() != null) {
1167        work = work || ops.contains(focus.getOperation());
1168        focus = focus.getOpNext();
1169      }
1170    }  
1171    if (!work) {
1172      return start;
1173    }
1174
1175    // entry point: tricky
1176    ExpressionNode group;
1177    if (ops.contains(start.getOperation())) {
1178      group = newGroup(lexer, start);
1179      group.setProximal(true);
1180      focus = start;
1181      start = group;
1182    } else {
1183      ExpressionNode node = start;
1184
1185      focus = node.getOpNext();
1186      while (!ops.contains(focus.getOperation())) {
1187        node = focus;
1188        focus = focus.getOpNext();
1189      }
1190      group = newGroup(lexer, focus);
1191      node.setOpNext(group);
1192    }
1193
1194    // now, at this point:
1195    //   group is the group we are adding to, it already has a .group property filled out.
1196    //   focus points at the group.group
1197    do {
1198      // run until we find the end of the sequence
1199      while (ops.contains(focus.getOperation())) {
1200        focus = focus.getOpNext();
1201      }
1202      if (focus.getOperation() != null) {
1203        group.setOperation(focus.getOperation());
1204        group.setOpNext(focus.getOpNext());
1205        focus.setOperation(null);
1206        focus.setOpNext(null);
1207        // now look for another sequence, and start it
1208        ExpressionNode node = group;
1209        focus = group.getOpNext();
1210        if (focus != null) { 
1211          while (focus != null && !ops.contains(focus.getOperation())) {
1212            node = focus;
1213            focus = focus.getOpNext();
1214          }
1215          if (focus != null) { // && (focus.Operation in Ops) - must be true 
1216            group = newGroup(lexer, focus);
1217            node.setOpNext(group);
1218          }
1219        }
1220      }
1221    }
1222    while (focus != null && focus.getOperation() != null);
1223    return start;
1224  }
1225
1226
1227  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
1228    ExpressionNode result = new ExpressionNode(lexer.nextId());
1229    result.setKind(Kind.Group);
1230    result.setGroup(next);
1231    result.getGroup().setProximal(true);
1232    return result;
1233  }
1234
1235  private Base processConstant(FHIRLexer lexer) throws FHIRLexerException {
1236    if (lexer.isStringConstant()) {
1237      return new StringType(processConstantString(lexer.take(), lexer)).noExtensions();
1238    } else if (Utilities.isInteger(lexer.getCurrent())) {
1239      return new IntegerType(lexer.take()).noExtensions();
1240    } else if (Utilities.isDecimal(lexer.getCurrent(), false)) {
1241      return new DecimalType(lexer.take()).noExtensions();
1242    } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) {
1243      return new BooleanType(lexer.take()).noExtensions();
1244    } else if (lexer.getCurrent().equals("{}")) {
1245      lexer.take();
1246      return null;
1247    } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) {
1248      return new FHIRConstant(lexer.take());
1249    } else {
1250      throw lexer.error("Invalid Constant "+lexer.getCurrent());
1251    }
1252  }
1253
1254  //  procedure CheckParamCount(c : integer);
1255  //  begin
1256  //    if exp.Parameters.Count <> c then
1257  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
1258  //  end;
1259
1260  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
1261    if (exp.getParameters().size() != count) {
1262      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString());
1263    }
1264    return true;
1265  }
1266
1267  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
1268    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax) {
1269      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString());
1270    }
1271    return true;
1272  }
1273
1274  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
1275    switch (exp.getFunction()) {
1276    case Empty: return checkParamCount(lexer, location, exp, 0);
1277    case Not: return checkParamCount(lexer, location, exp, 0);
1278    case Exists: return checkParamCount(lexer, location, exp, 0, 1);
1279    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
1280    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
1281    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
1282    case Distinct: return checkParamCount(lexer, location, exp, 0);
1283    case Count: return checkParamCount(lexer, location, exp, 0);
1284    case Where: return checkParamCount(lexer, location, exp, 1);
1285    case Select: return checkParamCount(lexer, location, exp, 1);
1286    case All: return checkParamCount(lexer, location, exp, 0, 1);
1287    case Repeat: return checkParamCount(lexer, location, exp, 1);
1288    case Aggregate: return checkParamCount(lexer, location, exp, 1, 2);
1289    case Item: return checkParamCount(lexer, location, exp, 1);
1290    case As: return checkParamCount(lexer, location, exp, 1);
1291    case OfType: return checkParamCount(lexer, location, exp, 1);
1292    case Type: return checkParamCount(lexer, location, exp, 0);
1293    case Is: return checkParamCount(lexer, location, exp, 1);
1294    case Single: return checkParamCount(lexer, location, exp, 0);
1295    case First: return checkParamCount(lexer, location, exp, 0);
1296    case Last: return checkParamCount(lexer, location, exp, 0);
1297    case Tail: return checkParamCount(lexer, location, exp, 0);
1298    case Skip: return checkParamCount(lexer, location, exp, 1);
1299    case Take: return checkParamCount(lexer, location, exp, 1);
1300    case Union: return checkParamCount(lexer, location, exp, 1);
1301    case Combine: return checkParamCount(lexer, location, exp, 1);
1302    case Intersect: return checkParamCount(lexer, location, exp, 1);
1303    case Exclude: return checkParamCount(lexer, location, exp, 1);
1304    case Iif: return checkParamCount(lexer, location, exp, 2,3);
1305    case Lower: return checkParamCount(lexer, location, exp, 0);
1306    case Upper: return checkParamCount(lexer, location, exp, 0);
1307    case ToChars: return checkParamCount(lexer, location, exp, 0);
1308    case IndexOf : return checkParamCount(lexer, location, exp, 1);
1309    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
1310    case StartsWith: return checkParamCount(lexer, location, exp, 1);
1311    case EndsWith: return checkParamCount(lexer, location, exp, 1);
1312    case Matches: return checkParamCount(lexer, location, exp, 1);
1313    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
1314    case Contains: return checkParamCount(lexer, location, exp, 1);
1315    case Replace: return checkParamCount(lexer, location, exp, 2);
1316    case Length: return checkParamCount(lexer, location, exp, 0);
1317    case Children: return checkParamCount(lexer, location, exp, 0);
1318    case Descendants: return checkParamCount(lexer, location, exp, 0);
1319    case MemberOf: return checkParamCount(lexer, location, exp, 1);
1320    case Trace: return checkParamCount(lexer, location, exp, 1, 2);
1321    case Check: return checkParamCount(lexer, location, exp, 2);
1322    case Today: return checkParamCount(lexer, location, exp, 0);
1323    case Now: return checkParamCount(lexer, location, exp, 0);
1324    case Resolve: return checkParamCount(lexer, location, exp, 0);
1325    case Extension: return checkParamCount(lexer, location, exp, 1);
1326    case AllFalse: return checkParamCount(lexer, location, exp, 0);
1327    case AnyFalse: return checkParamCount(lexer, location, exp, 0);
1328    case AllTrue: return checkParamCount(lexer, location, exp, 0);
1329    case AnyTrue: return checkParamCount(lexer, location, exp, 0);
1330    case HasValue: return checkParamCount(lexer, location, exp, 0);
1331    case Alias: return checkParamCount(lexer, location, exp, 1);
1332    case AliasAs: return checkParamCount(lexer, location, exp, 1);
1333    case Encode: return checkParamCount(lexer, location, exp, 1);
1334    case Decode: return checkParamCount(lexer, location, exp, 1);
1335    case Escape: return checkParamCount(lexer, location, exp, 1);
1336    case Unescape: return checkParamCount(lexer, location, exp, 1);
1337    case Trim: return checkParamCount(lexer, location, exp, 0);
1338    case Split: return checkParamCount(lexer, location, exp, 1);
1339    case Join: return checkParamCount(lexer, location, exp, 1);    
1340    case HtmlChecks1: return checkParamCount(lexer, location, exp, 0);
1341    case HtmlChecks2: return checkParamCount(lexer, location, exp, 0);
1342    case ToInteger: return checkParamCount(lexer, location, exp, 0);
1343    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
1344    case ToString: return checkParamCount(lexer, location, exp, 0);
1345    case ToQuantity: return checkParamCount(lexer, location, exp, 0);
1346    case ToBoolean: return checkParamCount(lexer, location, exp, 0);
1347    case ToDateTime: return checkParamCount(lexer, location, exp, 0);
1348    case ToTime: return checkParamCount(lexer, location, exp, 0);
1349    case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0);
1350    case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0);
1351    case ConvertsToString: return checkParamCount(lexer, location, exp, 0);
1352    case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0);
1353    case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0);
1354    case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0);
1355    case ConvertsToDate: return checkParamCount(lexer, location, exp, 0);
1356    case ConvertsToTime: return checkParamCount(lexer, location, exp, 0);
1357    case ConformsTo: return checkParamCount(lexer, location, exp, 1);
1358    case Round: return checkParamCount(lexer, location, exp, 0, 1); 
1359    case Sqrt: return checkParamCount(lexer, location, exp, 0); 
1360    case Abs: return checkParamCount(lexer, location, exp, 0);
1361    case Ceiling:  return checkParamCount(lexer, location, exp, 0);
1362    case Exp:  return checkParamCount(lexer, location, exp, 0);
1363    case Floor:  return checkParamCount(lexer, location, exp, 0);
1364    case Ln:  return checkParamCount(lexer, location, exp, 0);
1365    case Log:  return checkParamCount(lexer, location, exp, 1);
1366    case Power:  return checkParamCount(lexer, location, exp, 1);
1367    case Truncate: return checkParamCount(lexer, location, exp, 0);
1368    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
1369    }
1370    return false;
1371  }
1372
1373        private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
1374//    System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
1375    List<Base> work = new ArrayList<Base>();
1376    switch (exp.getKind()) {
1377    case Unary:
1378      work.add(new IntegerType(0));
1379      break;
1380    case Name:
1381      if (atEntry && exp.getName().equals("$this")) {
1382        work.add(context.getThisItem());
1383      } else if (atEntry && exp.getName().equals("$total")) {
1384        work.addAll(context.getTotal());
1385      } else if (atEntry && exp.getName().equals("$index")) {
1386        work.add(context.getIndex());
1387      } else {
1388        for (Base item : focus) {
1389          List<Base> outcome = execute(context, item, exp, atEntry);
1390          for (Base base : outcome) {
1391            if (base != null) {
1392              work.add(base);
1393            }
1394          }
1395        }     
1396      }
1397      break;
1398    case Function:
1399      List<Base> work2 = evaluateFunction(context, focus, exp);
1400      work.addAll(work2);
1401      break;
1402    case Constant:
1403      work.addAll(resolveConstant(context, exp.getConstant(), false, exp));
1404      break;
1405    case Group:
1406      work2 = execute(context, focus, exp.getGroup(), atEntry);
1407      work.addAll(work2);
1408    }
1409
1410    if (exp.getInner() != null) {
1411      work = execute(context, work, exp.getInner(), false);
1412    }
1413
1414    if (exp.isProximal() && exp.getOperation() != null) {
1415      ExpressionNode next = exp.getOpNext();
1416      ExpressionNode last = exp;
1417      while (next != null) {
1418        List<Base> work2 = preOperate(work, last.getOperation(), exp);
1419        if (work2 != null) {
1420          work = work2;
1421        }
1422        else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1423          work2 = executeTypeName(context, focus, next, false);
1424          work = operate(context, work, last.getOperation(), work2, last);
1425        } else {
1426          work2 = execute(context, focus, next, true);
1427          work = operate(context, work, last.getOperation(), work2, last);
1428//          System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString());
1429        }
1430        last = next;
1431        next = next.getOpNext();
1432      }
1433    }
1434//    System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString());
1435    return work;
1436  }
1437
1438  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
1439    List<Base> result = new ArrayList<Base>();
1440    if (next.getInner() != null) {
1441      result.add(new StringType(next.getName()+"."+next.getInner().getName()));
1442    } else { 
1443      result.add(new StringType(next.getName()));
1444    }
1445    return result;
1446  }
1447
1448
1449  private List<Base> preOperate(List<Base> left, Operation operation, ExpressionNode expr) throws PathEngineException {
1450    if (left.size() == 0) {
1451      return null;
1452    }
1453    switch (operation) {
1454    case And:
1455      return isBoolean(left, false) ? makeBoolean(false) : null;
1456    case Or:
1457      return isBoolean(left, true) ? makeBoolean(true) : null;
1458    case Implies:
1459      Equality v = asBool(left, expr); 
1460      return v == Equality.False ? makeBoolean(true) : null;
1461    default: 
1462      return null;
1463    }
1464  }
1465
1466  private List<Base> makeBoolean(boolean b) {
1467    List<Base> res = new ArrayList<Base>();
1468    res.add(new BooleanType(b).noExtensions());
1469    return res;
1470  }
1471
1472  private List<Base> makeNull() {
1473    List<Base> res = new ArrayList<Base>();
1474    return res;
1475  }
1476
1477  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1478    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
1479  }
1480
1481  private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1482    TypeDetails result = new TypeDetails(null);
1483    switch (exp.getKind()) {
1484    case Name:
1485      if (atEntry && exp.getName().equals("$this")) {
1486        result.update(context.getThisItem());
1487      } else if (atEntry && exp.getName().equals("$total")) {
1488        result.update(anything(CollectionStatus.UNORDERED));
1489      } else if (atEntry && exp.getName().equals("$index")) {
1490        result.addType(TypeDetails.FP_Integer);
1491      } else if (atEntry && focus == null) {
1492        result.update(executeContextType(context, exp.getName(), exp));
1493      } else {
1494        for (String s : focus.getTypes()) {
1495          result.update(executeType(s, exp, atEntry));
1496        }
1497        if (result.hasNoTypes()) { 
1498          throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe());
1499        }
1500      }
1501      break;
1502    case Function:
1503      result.update(evaluateFunctionType(context, focus, exp));
1504      break;
1505    case Unary:
1506      result.addType(TypeDetails.FP_Integer);
1507      result.addType(TypeDetails.FP_Decimal);
1508      result.addType(TypeDetails.FP_Quantity);
1509      break;
1510    case Constant:
1511      result.update(resolveConstantType(context, exp.getConstant(), exp));
1512      break;
1513    case Group:
1514      result.update(executeType(context, focus, exp.getGroup(), atEntry));
1515    }
1516    exp.setTypes(result);
1517
1518    if (exp.getInner() != null) {
1519      result = executeType(context, result, exp.getInner(), false);
1520    }
1521
1522    if (exp.isProximal() && exp.getOperation() != null) {
1523      ExpressionNode next = exp.getOpNext();
1524      ExpressionNode last = exp;
1525      while (next != null) {
1526        TypeDetails work;
1527        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1528          work = executeTypeName(context, focus, next, atEntry);
1529        } else {
1530          work = executeType(context, focus, next, atEntry);
1531        }
1532        result = operateTypes(result, last.getOperation(), work, last);
1533        last = next;
1534        next = next.getOpNext();
1535      }
1536      exp.setOpTypes(result);
1537    }
1538    return result;
1539  }
1540
1541  private List<Base> resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
1542    if (constant == null) {
1543      return new ArrayList<Base>();
1544    }
1545    if (!(constant instanceof FHIRConstant)) {
1546      return new ArrayList<Base>(Arrays.asList(constant));
1547    }
1548    FHIRConstant c = (FHIRConstant) constant;
1549    if (c.getValue().startsWith("%")) {
1550      return resolveConstant(context, c.getValue(), beforeContext, expr);
1551    } else if (c.getValue().startsWith("@")) {
1552      return new ArrayList<Base>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1), expr)));
1553    } else {
1554      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, c.getValue());
1555    }
1556  }
1557
1558  private Base processDateConstant(Object appInfo, String value, ExpressionNode expr) throws PathEngineException {
1559    String date = null;
1560    String time = null;
1561    String tz = null;
1562
1563    TemporalPrecisionEnum temp = null;
1564
1565    if (value.startsWith("T")) {
1566      time = value.substring(1);
1567    } else if (!value.contains("T")) {
1568      date = value;
1569    } else {
1570      String[] p = value.split("T");
1571      date = p[0];
1572      if (p.length > 1) {
1573        time = p[1];
1574      }
1575    }
1576    
1577    if (time != null) {
1578      int i = time.indexOf("-");
1579      if (i == -1) {
1580        i = time.indexOf("+");
1581      }
1582      if (i == -1) {
1583        i = time.indexOf("Z");
1584      }
1585      if (i > -1) {
1586        tz = time.substring(i);
1587        time = time.substring(0, i);
1588      }
1589      
1590      if (time.length() == 2) {
1591        time = time+":00:00";
1592        temp = TemporalPrecisionEnum.MINUTE;
1593      } else if (time.length() == 5) {
1594        temp = TemporalPrecisionEnum.MINUTE;
1595        time = time+":00";
1596      } else if (time.contains(".")) {
1597        temp = TemporalPrecisionEnum.MILLI;
1598      } else {
1599        temp = TemporalPrecisionEnum.SECOND;
1600      }
1601    }
1602    
1603    
1604    if (date == null) {
1605      if (tz != null) {
1606        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, value);
1607      } else {
1608        TimeType tt = new TimeType(time);
1609        tt.setPrecision(temp);
1610        return tt.noExtensions();
1611      }
1612    } else if (time != null) {
1613      DateTimeType dt = new DateTimeType(date+"T"+time+(tz == null ? "" : tz));
1614      dt.setPrecision(temp);
1615      return dt.noExtensions();
1616    } else { 
1617      return new DateType(date).noExtensions();
1618    }
1619  }
1620
1621
1622  private List<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
1623    if (s.equals("%sct")) {
1624      return new ArrayList<Base>(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions()));
1625    } else if (s.equals("%loinc")) {
1626      return new ArrayList<Base>(Arrays.asList(new StringType("http://loinc.org").noExtensions()));
1627    } else if (s.equals("%ucum")) {
1628      return new ArrayList<Base>(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions()));
1629    } else if (s.equals("%resource")) {
1630      if (context.focusResource == null) {
1631        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource");
1632      }
1633      return new ArrayList<Base>(Arrays.asList(context.focusResource));
1634    } else if (s.equals("%rootResource")) {
1635      if (context.rootResource == null) {
1636        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource");
1637      }
1638      return new ArrayList<Base>(Arrays.asList(context.rootResource));
1639    } else if (s.equals("%context")) {
1640      return new ArrayList<Base>(Arrays.asList(context.context));
1641    } else if (s.equals("%us-zip")) {
1642      return new ArrayList<Base>(Arrays.asList(new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions()));
1643    } else if (s.startsWith("%`vs-")) {
1644      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions()));
1645    } else if (s.startsWith("%`cs-")) {
1646      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions()));
1647    } else if (s.startsWith("%`ext-")) {
1648      return new ArrayList<Base>(Arrays.asList(new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions()));
1649    } else if (hostServices == null) {
1650      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
1651    } else {
1652      return hostServices.resolveConstant(context.appInfo, s.substring(1), beforeContext);
1653    }
1654  }
1655
1656
1657  private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException {
1658    StringBuilder b = new StringBuilder();
1659    int i = 1;
1660    while (i < s.length()-1) {
1661      char ch = s.charAt(i);
1662      if (ch == '\\') {
1663        i++;
1664        switch (s.charAt(i)) {
1665        case 't': 
1666          b.append('\t');
1667          break;
1668        case 'r':
1669          b.append('\r');
1670          break;
1671        case 'n': 
1672          b.append('\n');
1673          break;
1674        case 'f': 
1675          b.append('\f');
1676          break;
1677        case '\'':
1678          b.append('\'');
1679          break;
1680        case '"':
1681          b.append('"');
1682          break;
1683        case '`':
1684          b.append('`');
1685          break;
1686        case '\\': 
1687          b.append('\\');
1688          break;
1689        case '/': 
1690          b.append('/');
1691          break;
1692        case 'u':
1693          i++;
1694          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1695          b.append((char) uc);
1696          i = i + 3;
1697          break;
1698        default:
1699          throw lexer.error("Unknown character escape \\"+s.charAt(i));
1700        }
1701        i++;
1702      } else {
1703        b.append(ch);
1704        i++;
1705      }
1706    }
1707    return b.toString();
1708  }
1709
1710
1711  private List<Base> operate(ExecutionContext context, List<Base> left, Operation operation, List<Base> right, ExpressionNode holder) throws FHIRException {
1712    switch (operation) {
1713    case Equals: return opEquals(left, right, holder);
1714    case Equivalent: return opEquivalent(left, right, holder);
1715    case NotEquals: return opNotEquals(left, right, holder);
1716    case NotEquivalent: return opNotEquivalent(left, right, holder);
1717    case LessThan: return opLessThan(left, right, holder);
1718    case Greater: return opGreater(left, right, holder);
1719    case LessOrEqual: return opLessOrEqual(left, right, holder);
1720    case GreaterOrEqual: return opGreaterOrEqual(left, right, holder);
1721    case Union: return opUnion(left, right, holder);
1722    case In: return opIn(left, right, holder);
1723    case MemberOf: return opMemberOf(context, left, right, holder);
1724    case Contains: return opContains(left, right, holder);
1725    case Or:  return opOr(left, right, holder);
1726    case And:  return opAnd(left, right, holder);
1727    case Xor: return opXor(left, right, holder);
1728    case Implies: return opImplies(left, right, holder);
1729    case Plus: return opPlus(left, right, holder);
1730    case Times: return opTimes(left, right, holder);
1731    case Minus: return opMinus(left, right, holder);
1732    case Concatenate: return opConcatenate(left, right, holder);
1733    case DivideBy: return opDivideBy(left, right, holder);
1734    case Div: return opDiv(left, right, holder);
1735    case Mod: return opMod(left, right, holder);
1736    case Is: return opIs(left, right, holder);
1737    case As: return opAs(left, right, holder);
1738    default: 
1739      throw new Error("Not Done Yet: "+operation.toCode());
1740    }
1741  }
1742
1743  private List<Base> opAs(List<Base> left, List<Base> right, ExpressionNode expr) {
1744    List<Base> result = new ArrayList<>();
1745    if (right.size() != 1) {
1746      return result;
1747    } else {
1748      String tn = convertToString(right);
1749      for (Base nextLeft : left) {
1750        if (tn.equals(nextLeft.fhirType())) {
1751          result.add(nextLeft);
1752        }
1753      }
1754    }
1755    return result;
1756  }
1757
1758
1759  private List<Base> opIs(List<Base> left, List<Base> right, ExpressionNode expr) {
1760    List<Base> result = new ArrayList<Base>();
1761    if (left.size() == 0 || right.size() == 0) {
1762    } else if (left.size() != 1 || right.size() != 1) 
1763      result.add(new BooleanType(false).noExtensions());
1764    else {
1765      String tn = convertToString(right);
1766      if (left.get(0) instanceof org.hl7.fhir.r5.elementmodel.Element) {
1767        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1768      } else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions()) {
1769        result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions());
1770      } else {
1771        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1772      }
1773    }
1774    return result;
1775  }
1776
1777
1778  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
1779    switch (operation) {
1780    case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1781    case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1782    case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1783    case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1784    case LessThan: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1785    case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1786    case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1787    case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1788    case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1789    case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
1790    case Union: return left.union(right);
1791    case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1792    case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1793    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1794    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1795    case Times: 
1796      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
1797      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
1798        result.addType(TypeDetails.FP_Integer);
1799      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
1800        result.addType(TypeDetails.FP_Decimal);
1801      }
1802      return result;
1803    case DivideBy: 
1804      result = new TypeDetails(CollectionStatus.SINGLETON);
1805      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
1806        result.addType(TypeDetails.FP_Decimal);
1807      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
1808        result.addType(TypeDetails.FP_Decimal);
1809      }
1810      return result;
1811    case Concatenate:
1812      result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
1813      return result;
1814    case Plus:
1815      result = new TypeDetails(CollectionStatus.SINGLETON);
1816      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
1817        result.addType(TypeDetails.FP_Integer);
1818      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
1819        result.addType(TypeDetails.FP_Decimal);
1820      } else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri")) {
1821        result.addType(TypeDetails.FP_String);
1822      } else if (left.hasType(worker, "date", "dateTime", "instant")) {
1823        if (right.hasType(worker, "Quantity")) {
1824          result.addType(left.getType());
1825        } else {
1826          throw new PathEngineException(String.format("Error in date arithmetic: Unable to add type {0} to {1}", right.getType(), left.getType()), expr.getOpStart(), expr.toString());
1827        }
1828      }
1829      return result;
1830    case Minus:
1831      result = new TypeDetails(CollectionStatus.SINGLETON);
1832      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
1833        result.addType(TypeDetails.FP_Integer);
1834      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
1835        result.addType(TypeDetails.FP_Decimal);
1836      } else if (left.hasType(worker, "Quantity") && right.hasType(worker, "Quantity")) {
1837        result.addType(TypeDetails.FP_Quantity);
1838      } else if (left.hasType(worker, "date", "dateTime", "instant")) {
1839        if (right.hasType(worker, "Quantity")) {
1840          result.addType(left.getType());
1841        } else {
1842          throw new PathEngineException(String.format("Error in date arithmetic: Unable to subtract type {0} from {1}", right.getType(), left.getType()));
1843        }
1844      }
1845      return result;
1846    case Div: 
1847    case Mod: 
1848      result = new TypeDetails(CollectionStatus.SINGLETON);
1849      if (left.hasType(worker, "integer") && right.hasType(worker, "integer")) {
1850        result.addType(TypeDetails.FP_Integer);
1851      } else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal")) {
1852        result.addType(TypeDetails.FP_Decimal);
1853      }
1854      return result;
1855    case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1856    case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1857    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1858    default: 
1859      return null;
1860    }
1861  }
1862
1863
1864  private List<Base> opEquals(List<Base> left, List<Base> right, ExpressionNode expr) {
1865    if (left.size() == 0 || right.size() == 0) { 
1866      return new ArrayList<Base>();
1867    }
1868
1869    if (left.size() != right.size()) {
1870      return makeBoolean(false);
1871    }
1872
1873    boolean res = true;
1874    boolean nil = false;
1875    for (int i = 0; i < left.size(); i++) {
1876      Boolean eq = doEquals(left.get(i), right.get(i));
1877      if (eq == null) {
1878        nil = true;
1879      } else if (eq == false) { 
1880        res = false;
1881        break;
1882      }
1883    }
1884    if (!res) {
1885      return makeBoolean(res);
1886    } else if (nil) {
1887      return new ArrayList<Base>();
1888    } else {
1889      return makeBoolean(res);
1890    }
1891  }
1892
1893  private List<Base> opNotEquals(List<Base> left, List<Base> right, ExpressionNode expr) {
1894    if (!legacyMode && (left.size() == 0 || right.size() == 0)) {
1895      return new ArrayList<Base>();
1896    }
1897
1898    if (left.size() != right.size()) {
1899      return makeBoolean(true);
1900    }
1901
1902    boolean res = true;
1903    boolean nil = false;
1904    for (int i = 0; i < left.size(); i++) {
1905      Boolean eq = doEquals(left.get(i), right.get(i));
1906      if (eq == null) {
1907        nil = true;
1908      } else if (eq == true) { 
1909        res = false;
1910        break;
1911      }
1912    }
1913    if (!res) {
1914      return makeBoolean(res);
1915    } else if (nil) {
1916      return new ArrayList<Base>();
1917    } else {
1918      return makeBoolean(res);
1919    }
1920  }
1921
1922  private String removeTrailingZeros(String s) {
1923    if (Utilities.noString(s))
1924      return "";
1925    int i = s.length()-1;
1926    boolean done = false;
1927    boolean dot = false;
1928    while (i > 0 && !done) {
1929      if (s.charAt(i) == '.') {
1930        i--;
1931        dot = true;
1932      } else if (!dot && s.charAt(i) == '0') {
1933        i--;
1934      } else {
1935        done = true;
1936      }
1937    }
1938    return s.substring(0, i+1);
1939  }
1940
1941  private boolean decEqual(String left, String right) {
1942    left = removeTrailingZeros(left);
1943    right = removeTrailingZeros(right);
1944    return left.equals(right);
1945  }
1946  
1947  private Boolean datesEqual(BaseDateTimeType left, BaseDateTimeType right) {
1948    return left.equalsUsingFhirPathRules(right);
1949  }
1950  
1951  private Boolean doEquals(Base left, Base right) {
1952    if (left instanceof Quantity && right instanceof Quantity) {
1953      return qtyEqual((Quantity) left, (Quantity) right);
1954    } else if (left.isDateTime() && right.isDateTime()) { 
1955      return datesEqual(left.dateTimeValue(), right.dateTimeValue());
1956    } else if (left instanceof DecimalType || right instanceof DecimalType) { 
1957      return decEqual(left.primitiveValue(), right.primitiveValue());
1958    } else if (left.isPrimitive() && right.isPrimitive()) {
1959                        return Base.equals(left.primitiveValue(), right.primitiveValue());
1960    } else {
1961      return Base.compareDeep(left, right, false);
1962    }
1963  }
1964
1965  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
1966    if (left instanceof Quantity && right instanceof Quantity) {
1967      return qtyEquivalent((Quantity) left, (Quantity) right);
1968    }
1969    if (left.hasType("integer") && right.hasType("integer")) {
1970      return doEquals(left, right);
1971    }
1972    if (left.hasType("boolean") && right.hasType("boolean")) {
1973      return doEquals(left, right);
1974    }
1975    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
1976      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1977    }
1978    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant")) {
1979      Integer i = compareDateTimeElements(left, right, true);
1980      if (i == null) {
1981        i = 0;
1982      }
1983      return i == 0;
1984    }
1985    if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING)) {
1986      return Utilities.equivalent(convertToString(left), convertToString(right));
1987    }
1988    if (left.isPrimitive() && right.isPrimitive()) {
1989      return Utilities.equivalent(left.primitiveValue(), right.primitiveValue());
1990    }
1991    if (!left.isPrimitive() && !right.isPrimitive()) {
1992      MergedList<Property> props = new MergedList<Property>(left.children(), right.children(), new PropertyMatcher());
1993      for (MergeNode<Property> t : props) {
1994        if (t.hasLeft() && t.hasRight()) {
1995          if (t.getLeft().hasValues() && t.getRight().hasValues()) {
1996            MergedList<Base> values = new MergedList<Base>(t.getLeft().getValues(), t.getRight().getValues());
1997            for (MergeNode<Base> v : values) {
1998              if (v.hasLeft() && v.hasRight()) {
1999                if (!doEquivalent(v.getLeft(), v.getRight())) {
2000                  return false;
2001                }
2002              } else if (v.hasLeft() || v.hasRight()) {
2003                return false;
2004              }            
2005            }
2006          } else if (t.getLeft().hasValues() || t.getRight().hasValues()) {
2007            return false;
2008          }
2009        } else {
2010          return false;
2011        }
2012      }
2013      return true;
2014    } else {
2015      return false;
2016    }      
2017  }
2018
2019  private Boolean qtyEqual(Quantity left, Quantity right) {
2020    if (!left.hasValue() && !right.hasValue()) {
2021      return true;
2022    }
2023    if (!left.hasValue() || !right.hasValue()) {
2024      return null;
2025    }
2026    if (worker.getUcumService() != null) {
2027      Pair dl = qtyToCanonicalPair(left);
2028      Pair dr = qtyToCanonicalPair(right);
2029      if (dl != null && dr != null) {
2030        if (dl.getCode().equals(dr.getCode())) {
2031          return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));          
2032        } else {
2033          return false;
2034        }
2035      }
2036    }
2037    if (left.hasCode() || right.hasCode()) {
2038      if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
2039        return null;
2040      }
2041    } else if (!left.hasUnit() || right.hasUnit()) {
2042      if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
2043        return null;
2044      }
2045    }
2046    return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
2047  }
2048
2049  private Pair qtyToCanonicalPair(Quantity q) {
2050    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2051      return null;
2052    }
2053    try {
2054      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode());
2055      Pair c = worker.getUcumService().getCanonicalForm(p);
2056      return c;
2057    } catch (UcumException e) {
2058      return null;
2059    }
2060  }
2061
2062  private DecimalType qtyToCanonicalDecimal(Quantity q) {
2063    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2064      return null;
2065    }
2066    try {
2067      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode());
2068      Pair c = worker.getUcumService().getCanonicalForm(p);
2069      return new DecimalType(c.getValue().asDecimal());
2070    } catch (UcumException e) {
2071      return null;
2072    }
2073  }
2074
2075  private Base pairToQty(Pair p) {
2076    return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions();
2077  }
2078
2079
2080  private Pair qtyToPair(Quantity q) {
2081    if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
2082      return null;
2083    }
2084    try {
2085      return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
2086    } catch (UcumException e) {
2087      return null;
2088    }
2089  }
2090
2091
2092  private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
2093    if (!left.hasValue() && !right.hasValue()) {
2094      return true;
2095    }
2096    if (!left.hasValue() || !right.hasValue()) {
2097      return null;
2098    }
2099    if (worker.getUcumService() != null) {
2100      Pair dl = qtyToCanonicalPair(left);
2101      Pair dr = qtyToCanonicalPair(right);
2102      if (dl != null && dr != null) {
2103        if (dl.getCode().equals(dr.getCode())) {
2104          return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));          
2105        } else {
2106          return false;
2107        }
2108      }
2109    }
2110    if (left.hasCode() || right.hasCode()) {
2111      if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
2112        return null;
2113      }
2114    } else if (!left.hasUnit() || right.hasUnit()) {
2115      if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
2116        return null;
2117      }
2118    }
2119    return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
2120  }
2121
2122
2123
2124  private List<Base> opEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2125    if (left.size() != right.size()) {
2126      return makeBoolean(false);
2127    }
2128
2129    boolean res = true;
2130    for (int i = 0; i < left.size(); i++) {
2131      boolean found = false;
2132      for (int j = 0; j < right.size(); j++) {
2133        if (doEquivalent(left.get(i), right.get(j))) {
2134          found = true;
2135          break;
2136        }
2137      }
2138      if (!found) {
2139        res = false;
2140        break;
2141      }
2142    }
2143    return makeBoolean(res);
2144  }
2145
2146  private List<Base> opNotEquivalent(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2147    if (left.size() != right.size()) {
2148      return makeBoolean(true);
2149    }
2150
2151    boolean res = true;
2152    for (int i = 0; i < left.size(); i++) {
2153      boolean found = false;
2154      for (int j = 0; j < right.size(); j++) {
2155        if (doEquivalent(left.get(i), right.get(j))) {
2156          found = true;
2157          break;
2158        }
2159      }
2160      if (!found) {
2161        res = false;
2162        break;
2163      }
2164    }
2165    return makeBoolean(!res);
2166  }
2167
2168  private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url"};
2169
2170        private List<Base> opLessThan(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2171    if (left.size() == 0 || right.size() == 0) 
2172      return new ArrayList<Base>();
2173    
2174    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2175      Base l = left.get(0);
2176      Base r = right.get(0);
2177      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2178        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
2179      } else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) { 
2180        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
2181      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2182        Integer i = compareDateTimeElements(l, r, false);
2183        if (i == null) {
2184          return makeNull();
2185        } else {
2186          return makeBoolean(i < 0);
2187        }
2188      } else if ((l.hasType("time")) && (r.hasType("time"))) { 
2189        Integer i = compareTimeElements(l, r, false);
2190        if (i == null) {
2191          return makeNull();
2192        } else {
2193          return makeBoolean(i < 0);
2194        }
2195      } else {
2196        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2197      }
2198    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2199      List<Base> lUnit = left.get(0).listChildrenByName("code");
2200      List<Base> rUnit = right.get(0).listChildrenByName("code");
2201      if (Base.compareDeep(lUnit, rUnit, true)) {
2202        return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2203      } else {
2204        if (worker.getUcumService() == null) {
2205          return makeBoolean(false);
2206        } else {
2207          List<Base> dl = new ArrayList<Base>();
2208          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2209          List<Base> dr = new ArrayList<Base>();
2210          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2211          return opLessThan(dl, dr, expr);
2212        }
2213      }
2214    }
2215    return new ArrayList<Base>();
2216  }
2217
2218        private List<Base> opGreater(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2219    if (left.size() == 0 || right.size() == 0) 
2220      return new ArrayList<Base>();
2221    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2222      Base l = left.get(0);
2223      Base r = right.get(0);
2224      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) {
2225        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
2226      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2227        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
2228      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2229        Integer i = compareDateTimeElements(l, r, false);
2230        if (i == null) {
2231          return makeNull();
2232        } else {
2233          return makeBoolean(i > 0); 
2234        }
2235      } else if ((l.hasType("time")) && (r.hasType("time"))) { 
2236        Integer i = compareTimeElements(l, r, false);
2237        if (i == null) {
2238          return makeNull();
2239        } else {
2240          return makeBoolean(i > 0);
2241        }
2242      } else {
2243        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2244      }
2245    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2246      List<Base> lUnit = left.get(0).listChildrenByName("unit");
2247      List<Base> rUnit = right.get(0).listChildrenByName("unit");
2248      if (Base.compareDeep(lUnit, rUnit, true)) {
2249        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2250      } else {
2251        if (worker.getUcumService() == null) {
2252          return makeBoolean(false);
2253        } else {
2254          List<Base> dl = new ArrayList<Base>();
2255          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2256          List<Base> dr = new ArrayList<Base>();
2257          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2258          return opGreater(dl, dr, expr);
2259        }
2260      }
2261    }
2262    return new ArrayList<Base>();
2263  }
2264
2265        private List<Base> opLessOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2266    if (left.size() == 0 || right.size() == 0) { 
2267      return new ArrayList<Base>();
2268    }
2269    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2270      Base l = left.get(0);
2271      Base r = right.get(0);
2272      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2273        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
2274      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2275        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
2276      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2277        Integer i = compareDateTimeElements(l, r, false);
2278        if (i == null) {
2279          return makeNull();
2280        } else {
2281          return makeBoolean(i <= 0);
2282        }
2283      } else if ((l.hasType("time")) && (r.hasType("time"))) {
2284        Integer i = compareTimeElements(l, r, false);
2285        if (i == null) {
2286          return makeNull();
2287        } else {
2288          return makeBoolean(i <= 0);
2289        }
2290      } else {
2291        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2292      }
2293    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2294      List<Base> lUnits = left.get(0).listChildrenByName("unit");
2295      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
2296      List<Base> rUnits = right.get(0).listChildrenByName("unit");
2297      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
2298      if ((lunit == null && runit == null) || lunit.equals(runit)) {
2299        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2300      } else {
2301        if (worker.getUcumService() == null) {
2302          return makeBoolean(false);
2303        } else {
2304          List<Base> dl = new ArrayList<Base>();
2305          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2306          List<Base> dr = new ArrayList<Base>();
2307          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2308          return opLessOrEqual(dl, dr, expr);
2309        }
2310      }
2311    }
2312    return new ArrayList<Base>();
2313  }
2314
2315        private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2316    if (left.size() == 0 || right.size() == 0) { 
2317      return new ArrayList<Base>();
2318    }
2319    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
2320      Base l = left.get(0);
2321      Base r = right.get(0);
2322      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2323        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
2324      } else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) { 
2325        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
2326      } else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant"))) {
2327        Integer i = compareDateTimeElements(l, r, false);
2328        if (i == null) {
2329          return makeNull();
2330        } else {
2331          return makeBoolean(i >= 0);
2332        }
2333      } else if ((l.hasType("time")) && (r.hasType("time"))) {
2334        Integer i = compareTimeElements(l, r, false);
2335        if (i == null) {
2336          return makeNull();
2337        } else {
2338          return makeBoolean(i >= 0);
2339        }
2340      } else {
2341        throw makeException(expr, I18nConstants.FHIRPATH_CANT_COMPARE, l.fhirType(), r.fhirType());
2342      }
2343    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
2344      List<Base> lUnit = left.get(0).listChildrenByName("unit");
2345      List<Base> rUnit = right.get(0).listChildrenByName("unit");
2346      if (Base.compareDeep(lUnit, rUnit, true)) {
2347        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"), expr);
2348      } else {
2349        if (worker.getUcumService() == null) {
2350          return makeBoolean(false);
2351        } else {
2352          List<Base> dl = new ArrayList<Base>();
2353          dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
2354          List<Base> dr = new ArrayList<Base>();
2355          dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
2356          return opGreaterOrEqual(dl, dr, expr);
2357        }
2358      }
2359    }
2360    return new ArrayList<Base>();
2361  }
2362
2363        private List<Base> opMemberOf(ExecutionContext context, List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2364          boolean ans = false;
2365          String url = right.get(0).primitiveValue();
2366          ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, url) : worker.fetchResource(ValueSet.class, url);
2367          if (vs != null) {
2368            for (Base l : left) {
2369              if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
2370          if (worker.validateCode(terminologyServiceOptions , TypeConvertor.castToCoding(l), vs).isOk()) {
2371            ans = true;
2372          }
2373              } else if (l.fhirType().equals("Coding")) {
2374                if (worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk()) {
2375                  ans = true;
2376                }
2377              } else if (l.fhirType().equals("CodeableConcept")) {
2378                CodeableConcept cc = TypeConvertor.castToCodeableConcept(l);
2379            ValidationResult vr = worker.validateCode(terminologyServiceOptions, cc, vs);
2380            // System.out.println("~~~ "+DataRenderer.display(worker, cc)+ " memberOf "+url+": "+vr.toString());
2381            if (vr.isOk()) {
2382                  ans = true;
2383                }
2384              } else {
2385//              System.out.println("unknown type in opMemberOf: "+l.fhirType());
2386              }
2387            }
2388          }
2389          return makeBoolean(ans);
2390        }
2391
2392  private List<Base> opIn(List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
2393    if (left.size() == 0) { 
2394      return new ArrayList<Base>();
2395    }
2396    if (right.size() == 0) { 
2397      return makeBoolean(false);
2398    }
2399    boolean ans = true;
2400    for (Base l : left) {
2401      boolean f = false;
2402      for (Base r : right) {
2403        Boolean eq = doEquals(l, r);
2404        if (eq != null && eq == true) {
2405          f = true;
2406          break;
2407        }
2408      }
2409      if (!f) {
2410        ans = false;
2411        break;
2412      }
2413    }
2414    return makeBoolean(ans);
2415  }
2416
2417  private List<Base> opContains(List<Base> left, List<Base> right, ExpressionNode expr) {
2418    if (left.size() == 0 || right.size() == 0) { 
2419     return new ArrayList<Base>();
2420    }
2421    boolean ans = true;
2422    for (Base r : right) {
2423      boolean f = false;
2424      for (Base l : left) {
2425        Boolean eq = doEquals(l, r);
2426        if (eq != null && eq == true) {
2427          f = true;
2428          break;
2429        }
2430      }
2431      if (!f) {
2432        ans = false;
2433        break;
2434      }
2435    }
2436    return makeBoolean(ans);
2437  }
2438
2439  private List<Base> opPlus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2440    if (left.size() == 0 || right.size() == 0) { 
2441      return new ArrayList<Base>();
2442    }
2443    if (left.size() > 1) {
2444      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "+");
2445    }
2446    if (!left.get(0).isPrimitive()) {
2447      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "+", left.get(0).fhirType());
2448    }
2449    if (right.size() > 1) {
2450      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "+");
2451    }
2452    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"))) {
2453      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "+", right.get(0).fhirType());
2454    }
2455
2456    List<Base> result = new ArrayList<Base>();
2457    Base l = left.get(0);
2458    Base r = right.get(0);
2459    if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) { 
2460      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
2461    } else if (l.hasType("integer") && r.hasType("integer")) { 
2462      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
2463    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2464      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
2465    } else if (l.isDateTime() && r.hasType("Quantity")) {
2466      result.add(dateAdd((BaseDateTimeType) l, (Quantity) r, false, expr));
2467    } else {
2468      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType());
2469    }
2470    return result;
2471  }
2472
2473  private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) {
2474    BaseDateTimeType result = (BaseDateTimeType) d.copy();
2475    
2476    int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue();
2477    switch (q.hasCode() ? q.getCode() : q.getUnit()) {
2478    case "years": 
2479    case "year": 
2480      result.add(Calendar.YEAR, value);
2481      break;
2482    case "a":
2483      throw new PathEngineException(String.format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode()));
2484    case "months": 
2485    case "month": 
2486      result.add(Calendar.MONTH, value);
2487      break;
2488    case "mo":
2489      throw new PathEngineException(String.format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode()), holder.getOpStart(), holder.toString());
2490    case "weeks": 
2491    case "week": 
2492    case "wk":
2493      result.add(Calendar.DAY_OF_MONTH, value * 7);
2494      break;
2495    case "days": 
2496    case "day": 
2497    case "d":
2498      result.add(Calendar.DAY_OF_MONTH, value);
2499      break;
2500    case "hours": 
2501    case "hour": 
2502    case "h":
2503      result.add(Calendar.HOUR, value);
2504      break;
2505    case "minutes": 
2506    case "minute": 
2507    case "min":
2508      result.add(Calendar.MINUTE, value);
2509      break;
2510    case "seconds": 
2511    case "second": 
2512    case "s":
2513      result.add(Calendar.SECOND, value);
2514      break;
2515    case "milliseconds": 
2516    case "millisecond": 
2517    case "ms": 
2518      result.add(Calendar.MILLISECOND, value);
2519      break;
2520    default:
2521      throw new PathEngineException(String.format("Error in date arithmetic: unrecognized time unit %s", q.getCode()));
2522    }
2523    return result;
2524  }
2525
2526  private List<Base> opTimes(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2527    if (left.size() == 0 || right.size() == 0) {
2528      return new ArrayList<Base>();
2529    }
2530    if (left.size() > 1) {
2531      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "*");
2532    }
2533    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
2534      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType());
2535    }
2536    if (right.size() > 1) {
2537      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "*");
2538    }
2539    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
2540      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "*", right.get(0).fhirType());
2541    }
2542
2543    List<Base> result = new ArrayList<Base>();
2544    Base l = left.get(0);
2545    Base r = right.get(0);
2546
2547    if (l.hasType("integer") && r.hasType("integer")) { 
2548      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
2549    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2550      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
2551    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2552      Pair pl = qtyToPair((Quantity) l);
2553      Pair pr = qtyToPair((Quantity) r);
2554      Pair p;
2555      try {
2556        p = worker.getUcumService().multiply(pl, pr);
2557        result.add(pairToQty(p));
2558      } catch (UcumException e) {
2559        throw new PathEngineException(e.getMessage(), expr.getOpStart(), expr.toString(), e);
2560      }
2561    } else {
2562      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType());
2563    }
2564    return result;
2565  }
2566
2567
2568  private List<Base> opConcatenate(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2569    if (left.size() > 1) {
2570      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "&");
2571    }
2572    if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) {
2573      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType());
2574    }
2575    if (right.size() > 1) {
2576      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "&");
2577    }
2578    if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) {
2579      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType());
2580    }
2581
2582    List<Base> result = new ArrayList<Base>();
2583    String l = left.size() == 0 ? "" : left.get(0).primitiveValue();
2584    String r = right.size() == 0 ? "" : right.get(0).primitiveValue();
2585    result.add(new StringType(l + r));
2586    return result;
2587  }
2588
2589  private List<Base> opUnion(List<Base> left, List<Base> right, ExpressionNode expr) {
2590    List<Base> result = new ArrayList<Base>();
2591    for (Base item : left) {
2592      if (!doContains(result, item)) {
2593        result.add(item);
2594      }
2595    }
2596    for (Base item : right) {
2597      if (!doContains(result, item)) {
2598        result.add(item);
2599      }
2600    }
2601    return result;
2602  }
2603
2604  private boolean doContains(List<Base> list, Base item) {
2605    for (Base test : list) {
2606      Boolean eq = doEquals(test, item);
2607      if (eq != null && eq == true) {
2608        return true;
2609      }
2610    }
2611    return false;
2612  }
2613
2614
2615  private List<Base> opAnd(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2616    Equality l = asBool(left, expr);
2617    Equality r = asBool(right, expr);
2618    switch (l) {
2619    case False: return makeBoolean(false);
2620    case Null:
2621      if (r == Equality.False) {
2622        return makeBoolean(false);
2623      } else {
2624        return makeNull();
2625      }
2626    case True:
2627      switch (r) {
2628      case False: return makeBoolean(false);
2629      case Null: return makeNull();
2630      case True: return makeBoolean(true);
2631      }
2632    }
2633    return makeNull();
2634  }
2635
2636  private boolean isBoolean(List<Base> list, boolean b) {
2637    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
2638  }
2639
2640  private List<Base> opOr(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2641    Equality l = asBool(left, expr);
2642    Equality r = asBool(right, expr);
2643    switch (l) {
2644    case True: return makeBoolean(true);
2645    case Null:
2646      if (r == Equality.True) {
2647        return makeBoolean(true);
2648      } else {
2649        return makeNull();
2650      }
2651    case False:
2652      switch (r) {
2653      case False: return makeBoolean(false);
2654      case Null: return makeNull();
2655      case True: return makeBoolean(true);
2656      }
2657    }
2658    return makeNull();
2659  }
2660
2661  private List<Base> opXor(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2662    Equality l = asBool(left, expr);
2663    Equality r = asBool(right, expr);
2664    switch (l) {
2665    case True: 
2666      switch (r) {
2667      case False: return makeBoolean(true);
2668      case True: return makeBoolean(false);
2669      case Null: return makeNull();
2670      }
2671    case Null:
2672      return makeNull();
2673    case False:
2674      switch (r) {
2675      case False: return makeBoolean(false);
2676      case True: return makeBoolean(true);
2677      case Null: return makeNull();
2678      }
2679    }
2680    return makeNull();
2681  }
2682
2683  private List<Base> opImplies(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2684    Equality eq = asBool(left, expr);
2685    if (eq == Equality.False) { 
2686      return makeBoolean(true);
2687    } else if (right.size() == 0) {
2688      return makeNull();
2689    } else switch (asBool(right, expr)) {
2690    case False: return eq == Equality.Null ? makeNull() : makeBoolean(false);
2691    case Null: return makeNull();
2692    case True: return makeBoolean(true);
2693    }
2694    return makeNull();
2695  }
2696
2697
2698  private List<Base> opMinus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2699    if (left.size() == 0 || right.size() == 0) { 
2700      return new ArrayList<Base>();
2701    }
2702    if (left.size() > 1) {
2703      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "-");
2704    }
2705    if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) {
2706      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType());
2707    }
2708    if (right.size() > 1) {
2709      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "-");
2710    }
2711    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"))) {
2712      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType());
2713    }
2714
2715    List<Base> result = new ArrayList<Base>();
2716    Base l = left.get(0);
2717    Base r = right.get(0);
2718
2719    if (l.hasType("integer") && r.hasType("integer")) { 
2720      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
2721    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2722      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
2723    } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { 
2724      String s = l.primitiveValue();
2725      if ("0".equals(s)) {
2726        Quantity qty = (Quantity) r;
2727        result.add(qty.copy().setValue(qty.getValue().abs()));
2728      }
2729    } else if (l.isDateTime() && r.hasType("Quantity")) {
2730      result.add(dateAdd((BaseDateTimeType) l, (Quantity) r, true, expr));
2731    } else {
2732      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType());
2733    }
2734    return result;
2735  }
2736
2737  private List<Base> opDivideBy(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2738    if (left.size() == 0 || right.size() == 0) {
2739      return new ArrayList<Base>();
2740    }
2741    if (left.size() > 1) {
2742      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "/");
2743    }
2744    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
2745      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType());
2746    }
2747    if (right.size() > 1) {
2748      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "/");
2749    }
2750    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
2751      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType());
2752    }
2753
2754    List<Base> result = new ArrayList<Base>();
2755    Base l = left.get(0);
2756    Base r = right.get(0);
2757
2758    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
2759      Decimal d1;
2760      try {
2761        d1 = new Decimal(l.primitiveValue());
2762        Decimal d2 = new Decimal(r.primitiveValue());
2763        result.add(new DecimalType(d1.divide(d2).asDecimal()));
2764      } catch (UcumException e) {
2765        // just return nothing
2766      }
2767    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2768      Pair pl = qtyToPair((Quantity) l);
2769      Pair pr = qtyToPair((Quantity) r);
2770      Pair p;
2771      try {
2772        p = worker.getUcumService().divideBy(pl, pr);
2773        result.add(pairToQty(p));
2774      } catch (UcumException e) {
2775        // just return nothing
2776      }
2777    } else {
2778      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType());
2779    }
2780    return result;
2781  }
2782
2783  private List<Base> opDiv(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2784    if (left.size() == 0 || right.size() == 0) { 
2785      return new ArrayList<Base>();
2786    }
2787    if (left.size() > 1) {
2788      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "div");
2789    }
2790    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
2791      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType());
2792    }
2793    if (right.size() > 1) {
2794      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "div");
2795    }
2796    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
2797      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType());
2798    }
2799
2800    List<Base> result = new ArrayList<Base>();
2801    Base l = left.get(0);
2802    Base r = right.get(0);
2803
2804    if (l.hasType("integer") && r.hasType("integer")) {
2805      int divisor = Integer.parseInt(r.primitiveValue());
2806      if (divisor != 0) { 
2807        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor));
2808      }
2809    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2810      Decimal d1;
2811      try {
2812        d1 = new Decimal(l.primitiveValue());
2813        Decimal d2 = new Decimal(r.primitiveValue());
2814        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
2815      } catch (UcumException e) {
2816        // just return nothing
2817      }
2818    } else {
2819      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType());
2820    }
2821    return result;
2822  }
2823
2824  private List<Base> opMod(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2825    if (left.size() == 0 || right.size() == 0) {
2826      return new ArrayList<Base>();
2827    } if (left.size() > 1) {
2828      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_PLURAL, "mod");
2829    }
2830    if (!left.get(0).isPrimitive()) {
2831      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType());
2832    }
2833    if (right.size() > 1) {
2834      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_PLURAL, "mod");
2835    }
2836    if (!right.get(0).isPrimitive()) {
2837      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType());
2838    }
2839
2840    List<Base> result = new ArrayList<Base>();
2841    Base l = left.get(0);
2842    Base r = right.get(0);
2843
2844    if (l.hasType("integer") && r.hasType("integer")) { 
2845      int modulus = Integer.parseInt(r.primitiveValue());
2846      if (modulus != 0) {
2847        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus));
2848      }
2849    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
2850      Decimal d1;
2851      try {
2852        d1 = new Decimal(l.primitiveValue());
2853        Decimal d2 = new Decimal(r.primitiveValue());
2854        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
2855      } catch (UcumException e) {
2856        throw new PathEngineException(e);
2857      }
2858    } else {
2859      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType());
2860    }
2861    return result;
2862  }
2863
2864
2865  private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr) throws PathEngineException {
2866    if (constant instanceof BooleanType) { 
2867      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2868    } else if (constant instanceof IntegerType) {
2869      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2870    } else if (constant instanceof DecimalType) {
2871      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
2872    } else if (constant instanceof Quantity) {
2873      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
2874    } else if (constant instanceof FHIRConstant) {
2875      return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr);
2876    } else {
2877      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2878    }
2879  }
2880
2881  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr) throws PathEngineException {
2882    if (s.startsWith("@")) {
2883      if (s.startsWith("@T")) {
2884        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
2885      } else {
2886        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2887      }
2888    } else if (s.equals("%sct")) {
2889      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2890    } else if (s.equals("%loinc")) {
2891      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2892    } else if (s.equals("%ucum")) {
2893      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2894    } else if (s.equals("%resource")) {
2895      if (context.resource == null) {
2896        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource");
2897      }
2898      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
2899    } else if (s.equals("%rootResource")) {
2900      if (context.resource == null) {
2901        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus resource");
2902      }
2903      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
2904    } else if (s.equals("%context")) {
2905      return context.context;
2906    } else if (s.equals("%map-codes")) {
2907      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2908    } else if (s.equals("%us-zip")) {
2909      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2910    } else if (s.startsWith("%`vs-")) {
2911      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2912    } else if (s.startsWith("%`cs-")) {
2913      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2914    } else if (s.startsWith("%`ext-")) {
2915      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2916    } else if (hostServices == null) {
2917      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
2918    } else {
2919      return hostServices.resolveConstantType(context.appInfo, s);
2920    }
2921  }
2922
2923        private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
2924    List<Base> result = new ArrayList<Base>(); 
2925    if (atEntry && context.appInfo != null && hostServices != null) {
2926      // we'll see if the name matches a constant known by the context.
2927      List<Base> temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true);
2928      if (!temp.isEmpty()) {
2929        result.addAll(temp);
2930        return result;
2931      }
2932    }
2933    if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
2934      StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType());
2935      if (sd == null) {
2936        // logical model
2937        if (exp.getName().equals(item.fhirType())) {
2938          result.add(item);          
2939        }
2940      } else {
2941        while (sd != null) {
2942          if (sd.getType().equals(exp.getName())) {  
2943            result.add(item);
2944            break;
2945          }
2946          sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
2947        }
2948      }
2949    } else {
2950      getChildrenByName(item, exp.getName(), result);
2951    }
2952    if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) {
2953      // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
2954      // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
2955      result.addAll(hostServices.resolveConstant(context.appInfo, exp.getName(), false));
2956    }
2957    return result;
2958  }     
2959
2960  private String getParent(String rn) {
2961    return null;
2962  }
2963
2964
2965  private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr) throws PathEngineException, DefinitionException {
2966    if (hostServices == null) {
2967      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference");
2968    }
2969    return hostServices.resolveConstantType(context.appInfo, name);
2970  }
2971  
2972  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
2973    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up
2974      return new TypeDetails(CollectionStatus.SINGLETON, type);
2975    }
2976    TypeDetails result = new TypeDetails(null);
2977    getChildTypesByName(type, exp.getName(), result, exp);
2978    return result;
2979  }
2980
2981
2982  private String hashTail(String type) {
2983    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
2984  }
2985
2986
2987  @SuppressWarnings("unchecked")
2988  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
2989    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
2990    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) {
2991      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
2992    } else {
2993      for (ExpressionNode expr : exp.getParameters()) {
2994        if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate) {
2995          paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
2996        } else {
2997          paramTypes.add(executeType(context, focus, expr, true));
2998        }
2999      }
3000    }
3001    switch (exp.getFunction()) {
3002    case Empty : 
3003      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3004    case Not : 
3005      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3006    case Exists : { 
3007      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3008      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3009    }
3010    case SubsetOf : {
3011      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 
3012      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3013    }
3014    case SupersetOf : {
3015      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 
3016      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3017    }
3018    case IsDistinct : 
3019      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3020    case Distinct : 
3021      return focus;
3022    case Count : 
3023      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3024    case Where : 
3025      return focus;
3026    case Select : 
3027      return anything(focus.getCollectionStatus());
3028    case All : 
3029      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3030    case Repeat : 
3031      return anything(focus.getCollectionStatus());
3032    case Aggregate : 
3033      return anything(focus.getCollectionStatus());
3034    case Item : {
3035      checkOrdered(focus, "item", exp);
3036      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3037      return focus; 
3038    }
3039    case As : {
3040      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3041      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
3042    }
3043    case OfType : { 
3044      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3045      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
3046    }
3047    case Type : { 
3048      boolean s = false;
3049      boolean c = false;
3050      for (ProfiledType pt : focus.getProfiledTypes()) {
3051        s = s || pt.isSystemType();
3052        c = c || !pt.isSystemType();
3053      }
3054      if (s && c) {
3055        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo);
3056      } else if (s) {
3057        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo);
3058      } else {
3059        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo);
3060      }
3061    }
3062    case Is : {
3063      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3064      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3065    }
3066    case Single :
3067      return focus.toSingleton();
3068    case First : {
3069      checkOrdered(focus, "first", exp);
3070      return focus.toSingleton();
3071    }
3072    case Last : {
3073      checkOrdered(focus, "last", exp);
3074      return focus.toSingleton();
3075    }
3076    case Tail : {
3077      checkOrdered(focus, "tail", exp);
3078      return focus;
3079    }
3080    case Skip : {
3081      checkOrdered(focus, "skip", exp);
3082      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3083      return focus;
3084    }
3085    case Take : {
3086      checkOrdered(focus, "take", exp);
3087      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3088      return focus;
3089    }
3090    case Union : {
3091      return focus.union(paramTypes.get(0));
3092    }
3093    case Combine : {
3094      return focus.union(paramTypes.get(0));
3095    }
3096    case Intersect : {
3097      return focus.intersect(paramTypes.get(0));
3098    }
3099    case Exclude : {
3100      return focus;
3101    }
3102    case Iif : {
3103      TypeDetails types = new TypeDetails(null);
3104      types.update(paramTypes.get(0));
3105      if (paramTypes.size() > 1) {
3106        types.update(paramTypes.get(1));
3107      }
3108      return types;
3109    }
3110    case Lower : {
3111      checkContextString(focus, "lower", exp);
3112      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3113    }
3114    case Upper : {
3115      checkContextString(focus, "upper", exp);
3116      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3117    }
3118    case ToChars : {
3119      checkContextString(focus, "toChars", exp);
3120      return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 
3121    }
3122    case IndexOf : {
3123      checkContextString(focus, "indexOf", exp);
3124      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3125      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 
3126    }
3127    case Substring : {
3128      checkContextString(focus, "subString", exp);
3129      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3130      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3131    }
3132    case StartsWith : {
3133      checkContextString(focus, "startsWith", exp);
3134      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3135      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3136    }
3137    case EndsWith : {
3138      checkContextString(focus, "endsWith", exp);
3139      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3140      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3141    }
3142    case Matches : {
3143      checkContextString(focus, "matches", exp);
3144      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3145      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3146    }
3147    case ReplaceMatches : {
3148      checkContextString(focus, "replaceMatches", exp);
3149      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3150      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3151    }
3152    case Contains : {
3153      checkContextString(focus, "contains", exp);
3154      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3155      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3156    }
3157    case Replace : {
3158      checkContextString(focus, "replace", exp);
3159      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3160      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3161    }
3162    case Length : { 
3163      checkContextPrimitive(focus, "length", false, exp);
3164      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3165    }
3166    case Children : 
3167      return childTypes(focus, "*", exp);
3168    case Descendants : 
3169      return childTypes(focus, "**", exp);
3170    case MemberOf : {
3171      checkContextCoded(focus, "memberOf", exp);
3172      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3173      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3174    }
3175    case Trace : {
3176      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3177      return focus; 
3178    }
3179    case Check : {
3180      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3181      return focus; 
3182    }
3183    case Today : 
3184      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3185    case Now : 
3186      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3187    case Resolve : {
3188      checkContextReference(focus, "resolve", exp);
3189      return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 
3190    }
3191    case Extension : {
3192      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3193      return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 
3194    }
3195    case AnyTrue: 
3196      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3197    case AllTrue: 
3198      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3199    case AnyFalse: 
3200      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3201    case AllFalse: 
3202      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3203    case HasValue : 
3204      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3205    case HtmlChecks1 : 
3206      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3207    case HtmlChecks2 : 
3208      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3209    case Alias : 
3210      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3211      return anything(CollectionStatus.SINGLETON); 
3212    case AliasAs : 
3213      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3214      return focus;      
3215    case Encode:
3216      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3217      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3218    case Decode:
3219      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3220      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3221    case Escape:
3222      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3223      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3224    case Unescape:
3225      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3226      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3227    case Trim:
3228      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3229    case Split:
3230      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3231      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3232    case Join:
3233      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3234      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3235    case ToInteger : {
3236      checkContextPrimitive(focus, "toInteger", true, exp);
3237      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3238    }
3239    case ToDecimal : {
3240      checkContextPrimitive(focus, "toDecimal", true, exp);
3241      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
3242    }
3243    case ToString : {
3244      checkContextPrimitive(focus, "toString", true, exp);
3245      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3246    }
3247    case ToQuantity : {
3248      checkContextPrimitive(focus, "toQuantity", true, exp);
3249      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
3250    }
3251    case ToBoolean : {
3252      checkContextPrimitive(focus, "toBoolean", false, exp);
3253      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3254    }
3255    case ToDateTime : {
3256      checkContextPrimitive(focus, "ToDateTime", false, exp);
3257      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3258    }
3259    case ToTime : {
3260      checkContextPrimitive(focus, "ToTime", false, exp);
3261      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
3262    }
3263    case ConvertsToString : 
3264    case ConvertsToQuantity :{
3265      checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp);
3266      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3267    } 
3268    case ConvertsToInteger : 
3269    case ConvertsToDecimal : 
3270    case ConvertsToDateTime : 
3271    case ConvertsToDate : 
3272    case ConvertsToTime : 
3273    case ConvertsToBoolean : {
3274      checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp);
3275      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3276    }
3277    case ConformsTo: {
3278      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3279      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);       
3280    }
3281    case Abs : {
3282      checkContextNumerical(focus, "abs", exp);
3283      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3284    }
3285    case Truncate :
3286    case Floor : 
3287    case Ceiling : {
3288      checkContextDecimal(focus, exp.getFunction().toCode(), exp);
3289      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);       
3290    }  
3291      
3292    case Round :{
3293      checkContextDecimal(focus, "round", exp);
3294      if (paramTypes.size() > 0) {
3295        checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
3296      }
3297      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3298    } 
3299    
3300    case Exp : 
3301    case Ln : 
3302    case Sqrt : {
3303      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3304      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3305    }
3306    case Log :  {
3307      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3308      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3309      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3310    }
3311    case Power : {
3312      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3313      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3314      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3315    }
3316      
3317    case Custom : {
3318      return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
3319    }
3320    default:
3321      break;
3322    }
3323    throw new Error("not Implemented yet");
3324  }
3325
3326
3327  private void checkParamTypes(ExpressionNode expr, String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
3328    int i = 0;
3329    for (TypeDetails pt : typeSet) {
3330      if (i == paramTypes.size()) {
3331        return;
3332      }
3333      TypeDetails actual = paramTypes.get(i);
3334      i++;
3335      for (String a : actual.getTypes()) {
3336        if (!pt.hasType(worker, a)) {
3337          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString());
3338        }
3339      }
3340    }
3341  }
3342
3343  private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3344    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) {
3345      throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name);
3346    }
3347  }
3348
3349  private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3350    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) {
3351      throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe());
3352    }
3353  }
3354
3355
3356  private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3357    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) {
3358      throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe());
3359    }
3360  }
3361
3362
3363  private void checkContextString(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3364    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) {
3365      throw makeException(expr, I18nConstants.FHIRPATH_STRING_ONLY, name, focus.describe());
3366    }
3367  }
3368
3369
3370  private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException {
3371    if (canQty) {
3372       if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) {
3373         throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString());
3374       }
3375    } else if (!focus.hasType(primitiveTypes)) {
3376      throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString());
3377    }
3378  }
3379  
3380  private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3381    if (!focus.hasType("integer")  && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
3382      throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe());
3383    }    
3384  }
3385
3386  private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3387    if (!focus.hasType("decimal") && !focus.hasType("integer")) {
3388      throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe());
3389    }    
3390  }
3391
3392  private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
3393    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
3394    for (String f : focus.getTypes()) {
3395      getChildTypesByName(f, mask, result, expr);
3396    }
3397    return result;
3398  }
3399
3400  private TypeDetails anything(CollectionStatus status) {
3401    return new TypeDetails(status, allTypes.keySet());
3402  }
3403
3404  //    private boolean isPrimitiveType(String s) {
3405  //            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");
3406  //    }
3407
3408        private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3409    switch (exp.getFunction()) {
3410    case Empty : return funcEmpty(context, focus, exp);
3411    case Not : return funcNot(context, focus, exp);
3412    case Exists : return funcExists(context, focus, exp);
3413    case SubsetOf : return funcSubsetOf(context, focus, exp);
3414    case SupersetOf : return funcSupersetOf(context, focus, exp);
3415    case IsDistinct : return funcIsDistinct(context, focus, exp);
3416    case Distinct : return funcDistinct(context, focus, exp);
3417    case Count : return funcCount(context, focus, exp);
3418    case Where : return funcWhere(context, focus, exp);
3419    case Select : return funcSelect(context, focus, exp);
3420    case All : return funcAll(context, focus, exp);
3421    case Repeat : return funcRepeat(context, focus, exp);
3422    case Aggregate : return funcAggregate(context, focus, exp);
3423    case Item : return funcItem(context, focus, exp);
3424    case As : return funcAs(context, focus, exp);
3425    case OfType : return funcAs(context, focus, exp);
3426    case Type : return funcType(context, focus, exp);
3427    case Is : return funcIs(context, focus, exp);
3428    case Single : return funcSingle(context, focus, exp);
3429    case First : return funcFirst(context, focus, exp);
3430    case Last : return funcLast(context, focus, exp);
3431    case Tail : return funcTail(context, focus, exp);
3432    case Skip : return funcSkip(context, focus, exp);
3433    case Take : return funcTake(context, focus, exp);
3434    case Union : return funcUnion(context, focus, exp);
3435    case Combine : return funcCombine(context, focus, exp);
3436    case Intersect : return funcIntersect(context, focus, exp);
3437    case Exclude : return funcExclude(context, focus, exp);
3438    case Iif : return funcIif(context, focus, exp);
3439    case Lower : return funcLower(context, focus, exp);
3440    case Upper : return funcUpper(context, focus, exp);
3441    case ToChars : return funcToChars(context, focus, exp);
3442    case IndexOf : return funcIndexOf(context, focus, exp);
3443    case Substring : return funcSubstring(context, focus, exp);
3444    case StartsWith : return funcStartsWith(context, focus, exp);
3445    case EndsWith : return funcEndsWith(context, focus, exp);
3446    case Matches : return funcMatches(context, focus, exp);
3447    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
3448    case Contains : return funcContains(context, focus, exp);
3449    case Replace : return funcReplace(context, focus, exp);
3450    case Length : return funcLength(context, focus, exp);
3451    case Children : return funcChildren(context, focus, exp);
3452    case Descendants : return funcDescendants(context, focus, exp);
3453    case MemberOf : return funcMemberOf(context, focus, exp);
3454    case Trace : return funcTrace(context, focus, exp);
3455    case Check : return funcCheck(context, focus, exp);
3456    case Today : return funcToday(context, focus, exp);
3457    case Now : return funcNow(context, focus, exp);
3458    case Resolve : return funcResolve(context, focus, exp);
3459    case Extension : return funcExtension(context, focus, exp);
3460    case AnyFalse: return funcAnyFalse(context, focus, exp);
3461    case AllFalse: return funcAllFalse(context, focus, exp);
3462    case AnyTrue: return funcAnyTrue(context, focus, exp);
3463    case AllTrue: return funcAllTrue(context, focus, exp);
3464    case HasValue : return funcHasValue(context, focus, exp);
3465    case AliasAs : return funcAliasAs(context, focus, exp);
3466    case Encode : return funcEncode(context, focus, exp);
3467    case Decode : return funcDecode(context, focus, exp);
3468    case Escape : return funcEscape(context, focus, exp);
3469    case Unescape : return funcUnescape(context, focus, exp);
3470    case Trim : return funcTrim(context, focus, exp);
3471    case Split : return funcSplit(context, focus, exp);
3472    case Join : return funcJoin(context, focus, exp); 
3473    case Alias : return funcAlias(context, focus, exp);
3474    case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp);
3475    case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp);
3476    case ToInteger : return funcToInteger(context, focus, exp);
3477    case ToDecimal : return funcToDecimal(context, focus, exp);
3478    case ToString : return funcToString(context, focus, exp);
3479    case ToBoolean : return funcToBoolean(context, focus, exp);
3480    case ToQuantity : return funcToQuantity(context, focus, exp);
3481    case ToDateTime : return funcToDateTime(context, focus, exp);
3482    case ToTime : return funcToTime(context, focus, exp);
3483    case ConvertsToInteger : return funcIsInteger(context, focus, exp);
3484    case ConvertsToDecimal : return funcIsDecimal(context, focus, exp);
3485    case ConvertsToString : return funcIsString(context, focus, exp);
3486    case ConvertsToBoolean : return funcIsBoolean(context, focus, exp);
3487    case ConvertsToQuantity : return funcIsQuantity(context, focus, exp);
3488    case ConvertsToDateTime : return funcIsDateTime(context, focus, exp);
3489    case ConvertsToDate : return funcIsDate(context, focus, exp);
3490    case ConvertsToTime : return funcIsTime(context, focus, exp);
3491    case ConformsTo : return funcConformsTo(context, focus, exp);
3492    case Round : return funcRound(context, focus, exp); 
3493    case Sqrt : return funcSqrt(context, focus, exp); 
3494    case Abs : return funcAbs(context, focus, exp); 
3495    case Ceiling : return funcCeiling(context, focus, exp); 
3496    case Exp : return funcExp(context, focus, exp); 
3497    case Floor : return funcFloor(context, focus, exp); 
3498    case Ln : return funcLn(context, focus, exp); 
3499    case Log : return funcLog(context, focus, exp); 
3500    case Power : return funcPower(context, focus, exp); 
3501    case Truncate : return funcTruncate(context, focus, exp);
3502    
3503    case Custom: { 
3504      List<List<Base>> params = new ArrayList<List<Base>>();
3505      for (ExpressionNode p : exp.getParameters()) {
3506        params.add(execute(context, focus, p, true));
3507      }
3508      return hostServices.executeFunction(context.appInfo, focus, exp.getName(), params);
3509    }
3510    default:
3511      throw new Error("not Implemented yet");
3512    }
3513  }
3514        
3515        private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3516    if (focus.size() != 1) {
3517      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "sqrt", focus.size());
3518    }
3519    Base base = focus.get(0);
3520    List<Base> result = new ArrayList<Base>();
3521    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3522        Double d = Double.parseDouble(base.primitiveValue());
3523        try {
3524          result.add(new DecimalType(Math.sqrt(d)));
3525        } catch (Exception e) {
3526          // just return nothing
3527        }
3528    } else {
3529      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
3530    }
3531    return result;
3532        }
3533
3534
3535  private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3536    if (focus.size() != 1) {
3537      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "abs", focus.size());
3538    }
3539    Base base = focus.get(0);
3540    List<Base> result = new ArrayList<Base>();
3541    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3542        Double d = Double.parseDouble(base.primitiveValue());
3543        try {
3544          result.add(new DecimalType(Math.abs(d)));
3545        } catch (Exception e) {
3546          // just return nothing
3547        }
3548    } else if (base.hasType("Quantity")) {
3549      Quantity qty = (Quantity) base;
3550      result.add(qty.copy().setValue(qty.getValue().abs()));
3551    } else {
3552      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal");
3553    }
3554    return result;
3555  }
3556
3557
3558  private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3559    if (focus.size() != 1) {
3560      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "ceiling", focus.size());
3561    }
3562    Base base = focus.get(0);
3563    List<Base> result = new ArrayList<Base>();
3564    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3565        Double d = Double.parseDouble(base.primitiveValue());
3566        try {result.add(new IntegerType((int) Math.ceil(d)));
3567        } catch (Exception e) {
3568          // just return nothing
3569        }
3570    } else {
3571      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal");
3572    }
3573    return result;
3574  }
3575
3576  private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3577    if (focus.size() != 1) {
3578      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "floor", focus.size());
3579    }
3580    Base base = focus.get(0);
3581    List<Base> result = new ArrayList<Base>();
3582    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3583        Double d = Double.parseDouble(base.primitiveValue());
3584        try {
3585          result.add(new IntegerType((int) Math.floor(d)));
3586        } catch (Exception e) {
3587          // just return nothing
3588        }
3589    } else {
3590      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal");
3591    }
3592    return result;
3593  }
3594
3595
3596  private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3597    if (focus.size() != 1) {
3598      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "exp", focus.size());
3599    }
3600    Base base = focus.get(0);
3601    List<Base> result = new ArrayList<Base>();
3602    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3603        Double d = Double.parseDouble(base.primitiveValue());
3604        try {
3605          result.add(new DecimalType(Math.exp(d)));
3606        } catch (Exception e) {
3607          // just return nothing
3608        }
3609
3610    } else {
3611      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal");
3612    }
3613    return result;  
3614  }
3615
3616
3617  private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3618    if (focus.size() != 1) {
3619      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "ln", focus.size());
3620    }
3621    Base base = focus.get(0);
3622    List<Base> result = new ArrayList<Base>();
3623    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3624        Double d = Double.parseDouble(base.primitiveValue());
3625        try {
3626          result.add(new DecimalType(Math.log(d)));
3627        } catch (Exception e) {
3628          // just return nothing
3629        }        
3630    } else {
3631      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal");
3632    }
3633    return result;
3634  }
3635
3636
3637  private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3638    if (focus.size() != 1) {
3639      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "log", focus.size());
3640    }
3641    Base base = focus.get(0);
3642    List<Base> result = new ArrayList<Base>();
3643    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3644      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
3645      if (n1.size() != 1) {
3646        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal");
3647      }
3648      Double e = Double.parseDouble(n1.get(0).primitiveValue());
3649      Double d = Double.parseDouble(base.primitiveValue());
3650      try {
3651        result.add(new DecimalType(customLog(e, d)));
3652      } catch (Exception ex) {
3653        // just return nothing
3654      }
3655    } else {
3656      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal");
3657    }
3658    return result;
3659  }
3660  
3661  private static double customLog(double base, double logNumber) {
3662    return Math.log(logNumber) / Math.log(base);
3663  }
3664
3665  private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3666    if (focus.size() != 1) {
3667      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "power", focus.size());
3668    }
3669    Base base = focus.get(0);
3670    List<Base> result = new ArrayList<Base>();
3671    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3672      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
3673      if (n1.size() != 1) {
3674        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal");
3675      }
3676      Double e = Double.parseDouble(n1.get(0).primitiveValue());
3677        Double d = Double.parseDouble(base.primitiveValue());
3678        try {
3679          result.add(new DecimalType(Math.pow(d, e)));
3680        } catch (Exception ex) {
3681          // just return nothing
3682        }
3683    } else {
3684      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal");
3685    }
3686    return result;
3687  }
3688
3689  private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3690    if (focus.size() != 1) {
3691      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "truncate", focus.size());
3692    }
3693    Base base = focus.get(0);
3694    List<Base> result = new ArrayList<Base>();
3695    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3696        String s = base.primitiveValue();
3697        if (s.contains(".")) {
3698          s = s.substring(0, s.indexOf("."));
3699        }
3700        result.add(new IntegerType(s));
3701    } else {
3702      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
3703    }
3704    return result;
3705  }
3706
3707  private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
3708    if (focus.size() != 1) {
3709      throw makeException(expr, I18nConstants.FHIRPATH_FOCUS_PLURAL, "round", focus.size());
3710    }
3711    Base base = focus.get(0);
3712    List<Base> result = new ArrayList<Base>();
3713    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3714      int i = 0;
3715      if (expr.getParameters().size() == 1) {
3716        List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
3717        if (n1.size() != 1) {
3718          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer");
3719        }
3720        i = Integer.parseInt(n1.get(0).primitiveValue());
3721      }
3722      BigDecimal  d = new BigDecimal (base.primitiveValue());
3723      result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP)));
3724    } else {
3725      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal");
3726    }
3727    return result;
3728  }
3729
3730  private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
3731        public static String bytesToHex(byte[] bytes) {
3732          char[] hexChars = new char[bytes.length * 2];
3733          for (int j = 0; j < bytes.length; j++) {
3734            int v = bytes[j] & 0xFF;
3735            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
3736            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
3737          }
3738          return new String(hexChars);
3739        }
3740        
3741        public static byte[] hexStringToByteArray(String s) {
3742          int len = s.length();
3743          byte[] data = new byte[len / 2];
3744          for (int i = 0; i < len; i += 2) {
3745            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
3746          }
3747          return data;
3748        }
3749
3750        private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3751    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3752    String param = nl.get(0).primitiveValue();
3753
3754    List<Base> result = new ArrayList<Base>();
3755
3756    if (focus.size() == 1) {
3757      String cnt = focus.get(0).primitiveValue();
3758      if ("hex".equals(param)) {
3759        result.add(new StringType(bytesToHex(cnt.getBytes())));        
3760      } else if ("base64".equals(param)) {
3761        Base64.Encoder enc = Base64.getEncoder();
3762        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
3763      } else if ("urlbase64".equals(param)) {
3764        Base64.Encoder enc = Base64.getUrlEncoder();
3765        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
3766      }
3767    }
3768    return result;      
3769        }
3770
3771  private List<Base> funcDecode(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3772    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3773    String param = nl.get(0).primitiveValue();
3774
3775    List<Base> result = new ArrayList<Base>();
3776
3777    if (focus.size() == 1) {
3778      String cnt = focus.get(0).primitiveValue();
3779      if ("hex".equals(param)) {
3780        result.add(new StringType(new String(hexStringToByteArray(cnt))));        
3781      } else if ("base64".equals(param)) {
3782        Base64.Decoder enc = Base64.getDecoder();
3783        result.add(new StringType(new String(enc.decode(cnt))));
3784      } else if ("urlbase64".equals(param)) {
3785        Base64.Decoder enc = Base64.getUrlDecoder();
3786        result.add(new StringType(new String(enc.decode(cnt))));
3787      }
3788    }
3789
3790    return result;  
3791  }
3792
3793  private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3794    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3795    String param = nl.get(0).primitiveValue();
3796
3797    List<Base> result = new ArrayList<Base>();
3798    if (focus.size() == 1) {
3799      String cnt = focus.get(0).primitiveValue();
3800      if ("html".equals(param)) {
3801        result.add(new StringType(Utilities.escapeXml(cnt)));        
3802      } else if ("json".equals(param)) {
3803        result.add(new StringType(Utilities.escapeJson(cnt)));        
3804      }
3805    }
3806
3807    return result;  
3808  }
3809
3810  private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3811    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3812    String param = nl.get(0).primitiveValue();
3813
3814    List<Base> result = new ArrayList<Base>();
3815    if (focus.size() == 1) {
3816      String cnt = focus.get(0).primitiveValue();
3817      if ("html".equals(param)) {
3818        result.add(new StringType(Utilities.unescapeXml(cnt)));        
3819      } else if ("json".equals(param)) {
3820        result.add(new StringType(Utilities.unescapeJson(cnt)));        
3821      }
3822    }
3823
3824    return result;  
3825  }
3826
3827  private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3828    List<Base> result = new ArrayList<Base>();
3829    if (focus.size() == 1) {
3830      String cnt = focus.get(0).primitiveValue();
3831      result.add(new StringType(cnt.trim()));
3832    }
3833    return result;  
3834  }
3835
3836  private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3837    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3838    String param = nl.get(0).primitiveValue();
3839
3840    List<Base> result = new ArrayList<Base>();
3841    if (focus.size() == 1) {
3842      String cnt = focus.get(0).primitiveValue();
3843      for (String s : cnt.split(param)) {
3844        result.add(new StringType(s));
3845      }
3846    }
3847    return result;  
3848  }
3849
3850  private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3851    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3852    String param = nl.get(0).primitiveValue();
3853
3854    List<Base> result = new ArrayList<Base>();
3855    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param);
3856    for (Base i : focus) {
3857      b.append(i.primitiveValue());    
3858    }
3859    result.add(new StringType(b.toString()));
3860    return result;  
3861  }
3862        
3863  private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3864    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3865    String name = nl.get(0).primitiveValue();
3866    context.addAlias(name, focus);
3867    return focus;
3868  }
3869
3870  private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3871    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3872    String name = nl.get(0).primitiveValue();
3873    List<Base> res = new ArrayList<Base>();
3874    Base b = context.getAlias(name);
3875    if (b != null) {
3876      res.add(b);
3877    }
3878    return res;    
3879  }
3880
3881  private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3882    // todo: actually check the HTML
3883    if (focus.size() != 1) {
3884      return makeBoolean(false);          
3885    }
3886    XhtmlNode x = focus.get(0).getXhtml();
3887    if (x == null) {
3888      return makeBoolean(false);                
3889    }
3890    return makeBoolean(checkHtmlNames(x));    
3891  }
3892
3893  private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3894    // todo: actually check the HTML
3895    if (focus.size() != 1) {
3896      return makeBoolean(false);          
3897    }
3898    XhtmlNode x = focus.get(0).getXhtml();
3899    if (x == null) {
3900      return makeBoolean(false);                
3901    }
3902    return makeBoolean(checkForContent(x));    
3903  }
3904
3905  private boolean checkForContent(XhtmlNode x) {
3906    if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) {
3907      return true;
3908    }
3909    for (XhtmlNode c : x.getChildNodes()) {
3910      if (checkForContent(c)) {
3911        return true;
3912      }
3913    }
3914    return false;
3915  }
3916
3917
3918  private boolean checkHtmlNames(XhtmlNode node) {
3919    if (node.getNodeType() == NodeType.Comment) {
3920      if (node.getContent().startsWith("DOCTYPE"))
3921        return false;
3922    }
3923    if (node.getNodeType() == NodeType.Element) {
3924      if (!Utilities.existsInList(node.getName(),
3925          "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
3926          "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
3927          "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
3928          "code", "samp", "img", "map", "area")) {
3929        return false;
3930      }
3931      for (String an : node.getAttributes().keySet()) {
3932        boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
3933            "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
3934            // tables
3935            "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
3936
3937            Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
3938                "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
3939                "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
3940                "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
3941                "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
3942                );
3943        if (!ok) {
3944          return false;
3945        }
3946      }
3947      for (XhtmlNode c : node.getChildNodes()) {
3948        if (!checkHtmlNames(c)) {
3949          return false;
3950        }
3951      }
3952    }
3953    return true;
3954  }
3955  
3956  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3957    List<Base> result = new ArrayList<Base>();
3958    if (exp.getParameters().size() == 1) {
3959      List<Base> pc = new ArrayList<Base>();
3960      boolean all = true;
3961      for (Base item : focus) {
3962        pc.clear();
3963        pc.add(item);
3964        Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
3965        if (eq != Equality.True) {
3966          all = false;
3967          break;
3968        }
3969      }
3970      result.add(new BooleanType(all).noExtensions());
3971    } else {// (exp.getParameters().size() == 0) {
3972      boolean all = true;
3973      for (Base item : focus) {
3974        Equality eq = asBool(item, true);
3975        if (eq != Equality.True) {
3976          all = false;
3977          break;
3978        }
3979      }
3980      result.add(new BooleanType(all).noExtensions());
3981    }
3982    return result;
3983  }
3984
3985
3986  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
3987    return new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.aliases, newThis);
3988  }
3989
3990  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
3991    return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
3992  }
3993
3994
3995  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3996    List<Base> result = new ArrayList<Base>();
3997    result.add(DateTimeType.now());
3998    return result;
3999  }
4000
4001
4002  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4003    List<Base> result = new ArrayList<Base>();
4004    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
4005    return result;
4006  }
4007
4008
4009  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4010    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4011    if (nl.size() != 1 || focus.size() != 1) {
4012      return new ArrayList<Base>();
4013    }
4014    
4015    String url = nl.get(0).primitiveValue();
4016    ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, url) : worker.fetchResource(ValueSet.class, url);
4017    if (vs == null) {
4018      return new ArrayList<Base>();
4019    }
4020    Base l = focus.get(0);
4021    if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
4022      return makeBoolean(worker.validateCode(terminologyServiceOptions.guessSystem(), TypeConvertor.castToCoding(l), vs).isOk());
4023    } else if (l.fhirType().equals("Coding")) {
4024      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk());
4025    } else if (l.fhirType().equals("CodeableConcept")) {
4026      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk());
4027    } else {
4028//      System.out.println("unknown type in funcMemberOf: "+l.fhirType());
4029      return new ArrayList<Base>();
4030    }
4031  }
4032
4033
4034  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4035    List<Base> result = new ArrayList<Base>();
4036    List<Base> current = new ArrayList<Base>();
4037    current.addAll(focus);
4038    List<Base> added = new ArrayList<Base>();
4039    boolean more = true;
4040    while (more) {
4041      added.clear();
4042      for (Base item : current) {
4043        getChildrenByName(item, "*", added);
4044      }
4045      more = !added.isEmpty();
4046      result.addAll(added);
4047      current.clear();
4048      current.addAll(added);
4049    }
4050    return result;
4051  }
4052
4053
4054  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4055    List<Base> result = new ArrayList<Base>();
4056    for (Base b : focus) {
4057      getChildrenByName(b, "*", result);
4058    }
4059    return result;
4060  }
4061
4062
4063  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException {
4064    List<Base> result = new ArrayList<Base>();
4065
4066    if (focus.size() == 1) {
4067      String f = convertToString(focus.get(0));
4068      if (Utilities.noString(f)) {
4069        result.add(new StringType(""));
4070      } else {
4071        String t = convertToString(execute(context, focus, expr.getParameters().get(0), true));
4072        String r = convertToString(execute(context, focus, expr.getParameters().get(1), true));
4073        String n = f.replace(t, r);
4074        result.add(new StringType(n));
4075      }
4076    } else {
4077      throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size());
4078    }
4079    return result;
4080  }
4081
4082
4083  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4084    List<Base> result = new ArrayList<Base>();
4085    String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4086    String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
4087
4088    if (focus.size() == 1 && !Utilities.noString(regex)) {
4089      result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions());
4090    } else {
4091      result.add(new StringType(convertToString(focus.get(0))).noExtensions());
4092    }
4093    return result;
4094  }
4095
4096
4097  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4098    List<Base> result = new ArrayList<Base>();
4099    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4100
4101    if (focus.size() == 0) {
4102      result.add(new BooleanType(false).noExtensions());
4103    } else if (Utilities.noString(sw)) {
4104      result.add(new BooleanType(true).noExtensions());
4105    } else {
4106      if (focus.size() == 1 && !Utilities.noString(sw)) {
4107        result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
4108      } else {
4109        result.add(new BooleanType(false).noExtensions());
4110      }
4111    }
4112    return result;
4113  }
4114
4115
4116  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4117    List<Base> result = new ArrayList<Base>();
4118    result.add(new StringType(convertToString(focus)).noExtensions());
4119    return result;
4120  }
4121
4122  private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4123    List<Base> result = new ArrayList<Base>();
4124    if (focus.size() == 1) {
4125      if (focus.get(0) instanceof BooleanType) {
4126        result.add(focus.get(0));
4127      } else if (focus.get(0) instanceof IntegerType) {
4128        int i = Integer.parseInt(focus.get(0).primitiveValue());
4129        if (i == 0) {
4130          result.add(new BooleanType(false).noExtensions());
4131        } else if (i == 1) {
4132          result.add(new BooleanType(true).noExtensions());
4133        }
4134      } else if (focus.get(0) instanceof DecimalType) {
4135        if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) {
4136          result.add(new BooleanType(false).noExtensions());
4137        } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) {
4138          result.add(new BooleanType(true).noExtensions());
4139        }
4140      } else if (focus.get(0) instanceof StringType) {
4141        if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) {
4142          result.add(new BooleanType(true).noExtensions());
4143        } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) {
4144          result.add(new BooleanType(false).noExtensions()); 
4145        }
4146      }
4147    }
4148    return result;
4149  }
4150
4151  private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4152    List<Base> result = new ArrayList<Base>();
4153    if (focus.size() == 1) {
4154      if (focus.get(0) instanceof Quantity) {
4155        result.add(focus.get(0));
4156      } else if (focus.get(0) instanceof StringType) {
4157        Quantity q = parseQuantityString(focus.get(0).primitiveValue());
4158        if (q != null) {
4159          result.add(q.noExtensions());
4160        }
4161      } else if (focus.get(0) instanceof IntegerType) {
4162        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
4163      } else if (focus.get(0) instanceof DecimalType) {
4164        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
4165      }
4166    }
4167    return result;
4168  }
4169
4170  private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4171//  List<Base> result = new ArrayList<Base>();
4172//  result.add(new BooleanType(convertToBoolean(focus)));
4173//  return result;
4174  throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime");
4175}
4176
4177  private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4178//  List<Base> result = new ArrayList<Base>();
4179//  result.add(new BooleanType(convertToBoolean(focus)));
4180//  return result;
4181    throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime");
4182}
4183
4184
4185  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4186    String s = convertToString(focus);
4187    List<Base> result = new ArrayList<Base>();
4188    if (Utilities.isDecimal(s, true)) {
4189      result.add(new DecimalType(s).noExtensions());
4190    }
4191    if ("true".equals(s)) {
4192      result.add(new DecimalType(1).noExtensions());
4193    }
4194    if ("false".equals(s)) {
4195      result.add(new DecimalType(0).noExtensions());
4196    }
4197    return result;
4198  }
4199
4200
4201  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4202    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
4203    Equality v = asBool(n1, exp);
4204
4205    if (v == Equality.True) {
4206      return execute(context, focus, exp.getParameters().get(1), true);
4207    } else if (exp.getParameters().size() < 3) {
4208      return new ArrayList<Base>();
4209    } else {
4210      return execute(context, focus, exp.getParameters().get(2), true);
4211    }
4212  }
4213
4214
4215  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4216    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
4217    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
4218
4219    List<Base> result = new ArrayList<Base>();
4220    for (int i = 0; i < Math.min(focus.size(), i1); i++) {
4221      result.add(focus.get(i));
4222    }
4223    return result;
4224  }
4225
4226
4227  private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4228    List<Base> result = new ArrayList<Base>();
4229    for (Base item : focus) {
4230      if (!doContains(result, item)) {
4231        result.add(item);
4232      }
4233    }
4234    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
4235      if (!doContains(result, item)) {
4236        result.add(item);
4237      }
4238    }
4239    return result;
4240  }
4241
4242  private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4243    List<Base> result = new ArrayList<Base>();
4244    for (Base item : focus) {
4245      result.add(item);
4246    }
4247    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
4248      result.add(item);
4249    }
4250    return result;
4251  }
4252
4253  private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4254    List<Base> result = new ArrayList<Base>();
4255    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
4256    
4257    for (Base item : focus) {
4258      if (!doContains(result, item) && doContains(other, item)) {
4259        result.add(item);
4260      }
4261    }
4262    return result;    
4263  }
4264
4265  private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4266    List<Base> result = new ArrayList<Base>();
4267    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
4268    
4269    for (Base item : focus) {
4270      if (!doContains(other, item)) {
4271        result.add(item);
4272      }
4273    }
4274    return result;
4275  }
4276
4277
4278  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
4279    if (focus.size() == 1) {
4280      return focus;
4281    }
4282    throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size());
4283  }
4284
4285
4286  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
4287    if (focus.size() == 0 || focus.size() > 1) {
4288      return makeNull();
4289    }
4290    String ns = null;
4291    String n = null;
4292    
4293    ExpressionNode texp = expr.getParameters().get(0);
4294    if (texp.getKind() != Kind.Name) {
4295      throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is");
4296    }
4297    if (texp.getInner() != null) {
4298      if (texp.getInner().getKind() != Kind.Name) {
4299        throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is");
4300      }
4301      ns = texp.getName();
4302      n = texp.getInner().getName();
4303    } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) {
4304      ns = "System";
4305      n = texp.getName();
4306    } else {
4307      ns = "FHIR";
4308      n = texp.getName();        
4309    }
4310    if (ns.equals("System")) {
4311      if (focus.get(0) instanceof Resource) {
4312        return makeBoolean(false);
4313      }
4314      if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) {
4315        String t = Utilities.capitalize(focus.get(0).fhirType());
4316        if (n.equals(t)) {
4317          return makeBoolean(true);
4318        }
4319        if ("Date".equals(t) && n.equals("DateTime")) {
4320          return makeBoolean(true);
4321        } else { 
4322          return makeBoolean(false);
4323        }
4324      } else {
4325        return makeBoolean(false);
4326      }
4327    } else if (ns.equals("FHIR")) {
4328      return makeBoolean(n.equals(focus.get(0).fhirType()));
4329    } else { 
4330      return makeBoolean(false);
4331    }
4332  }
4333
4334
4335  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4336    List<Base> result = new ArrayList<Base>();
4337    String tn;
4338    if (expr.getParameters().get(0).getInner() != null) {
4339      tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName();
4340    } else {
4341      tn = "FHIR."+expr.getParameters().get(0).getName();
4342    }
4343    for (Base b : focus) {
4344      if (tn.startsWith("System.")) {
4345        if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 
4346          if (b.hasType(tn.substring(7))) {
4347            result.add(b);
4348          }
4349        }
4350
4351      } else if (tn.startsWith("FHIR.")) {
4352        if (b.hasType(tn.substring(5))) { 
4353          result.add(b);
4354        }
4355      }
4356    }
4357    return result;
4358  }
4359
4360  private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4361    List<Base> result = new ArrayList<Base>();
4362    for (Base item : focus) {
4363      result.add(new ClassTypeInfo(item));
4364    }
4365    return result;
4366  }
4367
4368
4369  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4370    List<Base> result = new ArrayList<Base>();
4371    List<Base> current = new ArrayList<Base>();
4372    current.addAll(focus);
4373    List<Base> added = new ArrayList<Base>();
4374    boolean more = true;
4375    while (more) {
4376      added.clear();
4377      List<Base> pc = new ArrayList<Base>();
4378      for (Base item : current) {
4379        pc.clear();
4380        pc.add(item);
4381        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
4382      }
4383      more = !added.isEmpty();
4384      result.addAll(added);
4385      current.clear();
4386      current.addAll(added);
4387    }
4388    return result;
4389  }
4390
4391
4392  private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4393    List<Base> total = new ArrayList<Base>();
4394    if (exp.parameterCount() > 1) {
4395      total = execute(context, focus, exp.getParameters().get(1), false);
4396    }
4397
4398    List<Base> pc = new ArrayList<Base>();
4399    for (Base item : focus) {
4400      ExecutionContext c = changeThis(context, item);
4401      c.total = total;
4402      c.next();
4403      total = execute(c, pc, exp.getParameters().get(0), true);
4404    }
4405    return total;
4406  }
4407
4408
4409
4410  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4411    if (focus.size() < 1) {
4412      return makeBoolean(true);
4413    }
4414    if (focus.size() == 1) {
4415      return makeBoolean(true);
4416    }
4417
4418    boolean distinct = true;
4419    for (int i = 0; i < focus.size(); i++) {
4420      for (int j = i+1; j < focus.size(); j++) {
4421        Boolean eq = doEquals(focus.get(j), focus.get(i));
4422        if (eq == null) {
4423          return new ArrayList<Base>();
4424        } else if (eq == true) {
4425          distinct = false;
4426          break;
4427        }
4428      }
4429    }
4430    return makeBoolean(distinct);
4431  }
4432
4433
4434  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4435    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
4436
4437    boolean valid = true;
4438    for (Base item : target) {
4439      boolean found = false;
4440      for (Base t : focus) {
4441        if (Base.compareDeep(item, t, false)) {
4442          found = true;
4443          break;
4444        }
4445      }
4446      if (!found) {
4447        valid = false;
4448        break;
4449      }
4450    }
4451    List<Base> result = new ArrayList<Base>();
4452    result.add(new BooleanType(valid).noExtensions());
4453    return result;
4454  }
4455
4456
4457  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4458    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
4459
4460    boolean valid = true;
4461    for (Base item : focus) {
4462      boolean found = false;
4463      for (Base t : target) {
4464        if (Base.compareDeep(item, t, false)) {
4465          found = true;
4466          break;
4467        }
4468      }
4469      if (!found) {
4470        valid = false;
4471        break;
4472      }
4473    }
4474    List<Base> result = new ArrayList<Base>();
4475    result.add(new BooleanType(valid).noExtensions());
4476    return result;
4477  }
4478
4479
4480  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4481    List<Base> result = new ArrayList<Base>();
4482    boolean empty = true;
4483    List<Base> pc = new ArrayList<Base>();
4484    for (Base f : focus) {
4485      if (exp.getParameters().size() == 1) {
4486        pc.clear();
4487        pc.add(f);
4488        Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp);
4489        if (v == Equality.True) {
4490          empty = false;
4491        }
4492      } else if (!f.isEmpty()) {
4493        empty = false;
4494      }
4495    }
4496    result.add(new BooleanType(!empty).noExtensions());
4497    return result;
4498  }
4499
4500
4501  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4502    List<Base> result = new ArrayList<Base>();
4503    Base refContext = null;
4504    for (Base item : focus) {
4505      String s = convertToString(item);
4506      if (item.fhirType().equals("Reference")) {
4507        refContext = item;
4508        Property p = item.getChildByName("reference");
4509        if (p != null && p.hasValues()) {
4510          s = convertToString(p.getValues().get(0));
4511        } else {
4512          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
4513        }
4514      }
4515      if (item.fhirType().equals("canonical")) {
4516        s = item.primitiveValue();
4517        refContext = item;
4518      }
4519      if (s != null) {
4520        Base res = null;
4521        if (s.startsWith("#")) {
4522          Property p = context.rootResource.getChildByName("contained");
4523          if (p != null) {
4524            for (Base c : p.getValues()) {
4525              if (chompHash(s).equals(chompHash(c.getIdBase()))) {
4526                res = c;
4527                break;
4528              }
4529            }
4530          }
4531        } else if (hostServices != null) {
4532          res = hostServices.resolveReference(context.appInfo, s, refContext);
4533        }
4534        if (res != null) {
4535          result.add(res);
4536        }
4537      }
4538    }
4539
4540    return result;
4541  }
4542
4543  /**
4544   * Strips a leading hashmark (#) if present at the start of a string
4545   */
4546  private String chompHash(String theId) {
4547    String retVal = theId;
4548    while (retVal.startsWith("#")) {
4549      retVal = retVal.substring(1);
4550    }
4551    return retVal;
4552  }
4553
4554  private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4555    List<Base> result = new ArrayList<Base>();
4556    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4557    String url = nl.get(0).primitiveValue();
4558
4559    for (Base item : focus) {
4560      List<Base> ext = new ArrayList<Base>();
4561      getChildrenByName(item, "extension", ext);
4562      getChildrenByName(item, "modifierExtension", ext);
4563      for (Base ex : ext) {
4564        List<Base> vl = new ArrayList<Base>();
4565        getChildrenByName(ex, "url", vl);
4566        if (convertToString(vl).equals(url)) {
4567          result.add(ex);
4568        }
4569      }
4570    }
4571    return result;
4572  }
4573
4574        private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4575          List<Base> result = new ArrayList<Base>();
4576          if (exp.getParameters().size() == 1) {
4577            boolean all = true;
4578            List<Base> pc = new ArrayList<Base>();
4579            for (Base item : focus) {
4580              pc.clear();
4581              pc.add(item);
4582              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
4583              Equality v = asBool(res, exp);
4584              if (v != Equality.False) {
4585                all = false;
4586                break;
4587              }
4588            }
4589            result.add(new BooleanType(all).noExtensions());
4590          } else { 
4591            boolean all = true;
4592            for (Base item : focus) {
4593               if (!canConvertToBoolean(item)) {
4594                  throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
4595                }
4596
4597              Equality v = asBool(item, true);
4598        if (v != Equality.False) {
4599                all = false;
4600                break;
4601              }
4602            }
4603            result.add(new BooleanType(all).noExtensions());
4604          }
4605          return result;
4606        }
4607  
4608        private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4609          List<Base> result = new ArrayList<Base>();
4610          if (exp.getParameters().size() == 1) {
4611            boolean any = false;
4612            List<Base> pc = new ArrayList<Base>();
4613            for (Base item : focus) {
4614              pc.clear();
4615              pc.add(item);
4616              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
4617              Equality v = asBool(res, exp);
4618        if (v == Equality.False) {
4619                any = true;
4620                break;
4621              }
4622            }
4623            result.add(new BooleanType(any).noExtensions());
4624          } else {
4625            boolean any = false;
4626            for (Base item : focus) {
4627               if (!canConvertToBoolean(item)) {
4628                  throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
4629                }
4630
4631              Equality v = asBool(item, true);
4632        if (v == Equality.False) {
4633                any = true;
4634                break;
4635              }
4636            }
4637            result.add(new BooleanType(any).noExtensions());
4638          }
4639          return result;
4640        }
4641  
4642        private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4643          List<Base> result = new ArrayList<Base>();
4644          if (exp.getParameters().size() == 1) {
4645            boolean all = true;
4646            List<Base> pc = new ArrayList<Base>();
4647            for (Base item : focus) {
4648              pc.clear();
4649              pc.add(item);
4650              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
4651              Equality v = asBool(res, exp);
4652        if (v != Equality.True) {
4653                all = false;
4654                break;
4655              }
4656            }
4657            result.add(new BooleanType(all).noExtensions());
4658          } else { 
4659            boolean all = true;
4660            for (Base item : focus) {
4661              if (!canConvertToBoolean(item)) {
4662                throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
4663              }
4664              Equality v = asBool(item, true);
4665              if (v != Equality.True) {
4666                all = false;
4667                break;
4668              }
4669            }
4670            result.add(new BooleanType(all).noExtensions());
4671          }
4672          return result;
4673        }
4674
4675        private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4676          List<Base> result = new ArrayList<Base>();
4677          if (exp.getParameters().size() == 1) {
4678            boolean any = false;
4679            List<Base> pc = new ArrayList<Base>();
4680            for (Base item : focus) {
4681              pc.clear();
4682              pc.add(item);
4683              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
4684              Equality v = asBool(res, exp);
4685        if (v == Equality.True) {
4686                any = true;
4687                break;
4688              }
4689            }
4690            result.add(new BooleanType(any).noExtensions());
4691          } else {
4692            boolean any = false;
4693      for (Base item : focus) {
4694        if (!canConvertToBoolean(item)) {
4695          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
4696        }
4697
4698        Equality v = asBool(item, true);
4699        if (v == Equality.True) {
4700                  any = true;
4701                  break;
4702                }
4703      }
4704      result.add(new BooleanType(any).noExtensions());
4705          }
4706          return result;
4707        }
4708
4709        private boolean canConvertToBoolean(Base item) {
4710    return (item.isBooleanPrimitive());
4711  }
4712
4713  private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4714    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4715    String name = nl.get(0).primitiveValue();
4716    if (exp.getParameters().size() == 2) {
4717      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
4718      log(name, n2);
4719    } else { 
4720      log(name, focus);
4721    }
4722    return focus;
4723  }
4724
4725  private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
4726    List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4727    if (!convertToBoolean(n1)) {
4728      List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true);
4729      String name = n2.get(0).primitiveValue();
4730      throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name);
4731    }
4732    return focus;
4733  }
4734
4735  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4736    if (focus.size() <= 1) {
4737      return focus;
4738    }
4739
4740    List<Base> result = new ArrayList<Base>();
4741    for (int i = 0; i < focus.size(); i++) {
4742      boolean found = false;
4743      for (int j = i+1; j < focus.size(); j++) {
4744        Boolean eq = doEquals(focus.get(j), focus.get(i));
4745        if (eq == null)
4746          return new ArrayList<Base>();
4747        else if (eq == true) {
4748          found = true;
4749          break;
4750        }
4751      }
4752      if (!found) {
4753        result.add(focus.get(i));
4754      }
4755    }
4756    return result;
4757  }
4758
4759        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4760    List<Base> result = new ArrayList<Base>();
4761    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4762
4763    if (focus.size() == 1 && !Utilities.noString(sw)) {
4764      String st = convertToString(focus.get(0));
4765      if (Utilities.noString(st)) {
4766        result.add(new BooleanType(false).noExtensions());
4767      } else {
4768        boolean ok = st.matches("(?s)" + sw);
4769        result.add(new BooleanType(ok).noExtensions());
4770      }
4771    } else {
4772      result.add(new BooleanType(false).noExtensions());
4773    }
4774    return result;
4775  }
4776
4777        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4778    List<Base> result = new ArrayList<Base>();
4779    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4780
4781    if (focus.size() != 1) {
4782      result.add(new BooleanType(false).noExtensions());
4783    } else if (Utilities.noString(sw)) {
4784      result.add(new BooleanType(true).noExtensions());
4785    } else {
4786      String st = convertToString(focus.get(0));
4787      if (Utilities.noString(st)) {
4788        result.add(new BooleanType(false).noExtensions());
4789      } else {
4790        result.add(new BooleanType(st.contains(sw)).noExtensions());
4791      }
4792    } 
4793    return result;
4794  }
4795
4796  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4797    List<Base> result = new ArrayList<Base>();
4798    if (focus.size() == 1) {
4799      String s = convertToString(focus.get(0));
4800      result.add(new IntegerType(s.length()).noExtensions());
4801    }
4802    return result;
4803  }
4804
4805  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4806    List<Base> result = new ArrayList<Base>();
4807    if (focus.size() == 1) {
4808      String s = convertToString(focus.get(0));
4809      result.add(new BooleanType(!Utilities.noString(s)).noExtensions());
4810    } else {
4811      result.add(new BooleanType(false).noExtensions());
4812    }
4813    return result;
4814  }
4815
4816        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4817    List<Base> result = new ArrayList<Base>();
4818    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4819
4820    if (focus.size() == 0) {
4821      result.add(new BooleanType(false).noExtensions());
4822    } else if (Utilities.noString(sw)) {
4823      result.add(new BooleanType(true).noExtensions());
4824    } else {
4825      String s = convertToString(focus.get(0));
4826      if (s == null) {
4827        result.add(new BooleanType(false).noExtensions());
4828      } else {
4829        result.add(new BooleanType(s.startsWith(sw)).noExtensions());
4830      }
4831    }
4832    return result;
4833  }
4834
4835  private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4836    List<Base> result = new ArrayList<Base>();
4837    if (focus.size() == 1) {
4838      String s = convertToString(focus.get(0));
4839      if (!Utilities.noString(s)) { 
4840        result.add(new StringType(s.toLowerCase()).noExtensions());
4841      }
4842    }
4843    return result;
4844  }
4845
4846  private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4847    List<Base> result = new ArrayList<Base>();
4848    if (focus.size() == 1) {
4849      String s = convertToString(focus.get(0));
4850      if (!Utilities.noString(s)) { 
4851        result.add(new StringType(s.toUpperCase()).noExtensions());
4852      }
4853    }
4854    return result;
4855  }
4856
4857  private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4858    List<Base> result = new ArrayList<Base>();
4859    if (focus.size() == 1) {
4860      String s = convertToString(focus.get(0));
4861      for (char c : s.toCharArray()) {  
4862        result.add(new StringType(String.valueOf(c)).noExtensions());
4863      }
4864    }
4865    return result;
4866  }
4867  
4868  private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4869    List<Base> result = new ArrayList<Base>();
4870    
4871    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
4872    if (focus.size() == 0) {
4873      result.add(new IntegerType(0).noExtensions());
4874    } else if (Utilities.noString(sw)) {
4875      result.add(new IntegerType(0).noExtensions());
4876    } else {
4877      String s = convertToString(focus.get(0));
4878      if (s == null) {
4879        result.add(new IntegerType(0).noExtensions());
4880      } else {
4881        result.add(new IntegerType(s.indexOf(sw)).noExtensions());
4882      }
4883    }
4884    return result;
4885  }
4886
4887        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4888    List<Base> result = new ArrayList<Base>();
4889    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
4890    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
4891    int i2 = -1;
4892    if (exp.parameterCount() == 2) {
4893      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
4894      i2 = Integer.parseInt(n2.get(0).primitiveValue());
4895    }
4896
4897    if (focus.size() == 1) {
4898      String sw = convertToString(focus.get(0));
4899      String s;
4900      if (i1 < 0 || i1 >= sw.length()) {
4901        return new ArrayList<Base>();
4902      }
4903      if (exp.parameterCount() == 2) {
4904        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
4905      } else {
4906        s = sw.substring(i1);
4907      }
4908      if (!Utilities.noString(s)) { 
4909        result.add(new StringType(s).noExtensions());
4910      }
4911    }
4912    return result;
4913  }
4914
4915  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4916    String s = convertToString(focus);
4917    List<Base> result = new ArrayList<Base>();
4918    if (Utilities.isInteger(s)) {
4919      result.add(new IntegerType(s).noExtensions());
4920    } else if ("true".equals(s)) {
4921      result.add(new IntegerType(1).noExtensions());
4922    } else if ("false".equals(s)) {
4923      result.add(new IntegerType(0).noExtensions());
4924    }
4925    return result;
4926  }
4927
4928  private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4929    List<Base> result = new ArrayList<Base>();
4930    if (focus.size() != 1) {
4931      result.add(new BooleanType(false).noExtensions());
4932    } else if (focus.get(0) instanceof IntegerType) {
4933      result.add(new BooleanType(true).noExtensions());
4934    } else if (focus.get(0) instanceof BooleanType) {
4935      result.add(new BooleanType(true).noExtensions());
4936    } else if (focus.get(0) instanceof StringType) {
4937      result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions());
4938    } else { 
4939      result.add(new BooleanType(false).noExtensions());
4940    }
4941    return result;
4942  }
4943
4944  private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4945    List<Base> result = new ArrayList<Base>();
4946    if (focus.size() != 1) {
4947      result.add(new BooleanType(false).noExtensions());
4948    } else if (focus.get(0) instanceof IntegerType) {
4949      result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions());
4950    } else if (focus.get(0) instanceof DecimalType) {
4951      result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions());
4952    } else if (focus.get(0) instanceof BooleanType) {
4953      result.add(new BooleanType(true).noExtensions());
4954    } else if (focus.get(0) instanceof StringType) {
4955      result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions());
4956    } else { 
4957      result.add(new BooleanType(false).noExtensions());
4958    }
4959    return result;
4960  }
4961
4962  private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4963    List<Base> result = new ArrayList<Base>();
4964    if (focus.size() != 1) {
4965      result.add(new BooleanType(false).noExtensions());
4966    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
4967      result.add(new BooleanType(true).noExtensions());
4968    } else if (focus.get(0) instanceof StringType) {
4969      result.add(new BooleanType((convertToString(focus.get(0)).matches
4970          ("([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());
4971    } else { 
4972      result.add(new BooleanType(false).noExtensions());
4973    }
4974    return result;
4975  }
4976
4977  private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4978    List<Base> result = new ArrayList<Base>();
4979    if (focus.size() != 1) {
4980      result.add(new BooleanType(false).noExtensions());
4981    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
4982      result.add(new BooleanType(true).noExtensions());
4983    } else if (focus.get(0) instanceof StringType) {
4984      result.add(new BooleanType((convertToString(focus.get(0)).matches
4985          ("([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());
4986    } else { 
4987      result.add(new BooleanType(false).noExtensions());
4988    }
4989    return result;
4990  }
4991
4992  private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
4993    if (hostServices == null) {
4994      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo");
4995    }
4996    List<Base> result = new ArrayList<Base>();
4997    if (focus.size() != 1) {
4998      result.add(new BooleanType(false).noExtensions());
4999    } else {
5000      String url = convertToString(execute(context, focus, expr.getParameters().get(0), true));
5001      result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo,  focus.get(0), url)).noExtensions());
5002    }
5003    return result;
5004  }
5005
5006  private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5007    List<Base> result = new ArrayList<Base>();
5008    if (focus.size() != 1) {
5009      result.add(new BooleanType(false).noExtensions());
5010    } else if (focus.get(0) instanceof TimeType) {
5011      result.add(new BooleanType(true).noExtensions());
5012    } else if (focus.get(0) instanceof StringType) {
5013      result.add(new BooleanType((convertToString(focus.get(0)).matches
5014          ("(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());
5015    } else {
5016      result.add(new BooleanType(false).noExtensions());
5017    }
5018    return result;
5019  }
5020
5021  private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5022    List<Base> result = new ArrayList<Base>();
5023    if (focus.size() != 1) {
5024      result.add(new BooleanType(false).noExtensions());
5025    } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) {
5026      result.add(new BooleanType(true).noExtensions());
5027    } else { 
5028      result.add(new BooleanType(false).noExtensions());
5029    }
5030    return result;
5031  }
5032
5033  private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5034    List<Base> result = new ArrayList<Base>();
5035    if (focus.size() != 1) {
5036      result.add(new BooleanType(false).noExtensions());
5037    } else if (focus.get(0) instanceof IntegerType) {
5038      result.add(new BooleanType(true).noExtensions());
5039    } else if (focus.get(0) instanceof DecimalType) {
5040      result.add(new BooleanType(true).noExtensions());
5041    } else if (focus.get(0) instanceof Quantity) {
5042      result.add(new BooleanType(true).noExtensions());
5043    } else if (focus.get(0) instanceof BooleanType) {
5044      result.add(new BooleanType(true).noExtensions());
5045    } else  if (focus.get(0) instanceof StringType) {
5046      Quantity q = parseQuantityString(focus.get(0).primitiveValue());
5047      result.add(new BooleanType(q != null).noExtensions());
5048    } else {
5049      result.add(new BooleanType(false).noExtensions());
5050    }
5051    return result;
5052  }
5053
5054  public Quantity parseQuantityString(String s) {
5055    if (s == null) {
5056      return null;
5057    }
5058    s = s.trim();
5059    if (s.contains(" ")) {
5060      String v = s.substring(0, s.indexOf(" ")).trim();
5061      s = s.substring(s.indexOf(" ")).trim();
5062      if (!Utilities.isDecimal(v, false)) {
5063        return null;
5064      }
5065      if (s.startsWith("'") && s.endsWith("'")) {
5066        return Quantity.fromUcum(v, s.substring(1, s.length()-1));
5067      }
5068      if (s.equals("year") || s.equals("years")) {
5069        return Quantity.fromUcum(v, "a");
5070      } else if (s.equals("month") || s.equals("months")) {
5071        return Quantity.fromUcum(v, "mo_s");
5072      } else if (s.equals("week") || s.equals("weeks")) {
5073        return Quantity.fromUcum(v, "wk");
5074      } else if (s.equals("day") || s.equals("days")) {
5075        return Quantity.fromUcum(v, "d");
5076      } else if (s.equals("hour") || s.equals("hours")) {
5077        return Quantity.fromUcum(v, "h");
5078      } else if (s.equals("minute") || s.equals("minutes")) {
5079        return Quantity.fromUcum(v, "min");
5080      } else if (s.equals("second") || s.equals("seconds")) {
5081        return Quantity.fromUcum(v, "s");
5082      } else if (s.equals("millisecond") || s.equals("milliseconds")) {
5083        return Quantity.fromUcum(v, "ms");
5084      } else {
5085        return null;
5086      } 
5087    } else {
5088      if (Utilities.isDecimal(s, true)) {
5089        return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1");
5090      } else {
5091        return null;
5092      } 
5093    }
5094  }
5095
5096
5097  private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5098    List<Base> result = new ArrayList<Base>();
5099    if (focus.size() != 1) {
5100      result.add(new BooleanType(false).noExtensions());
5101    } else if (focus.get(0) instanceof IntegerType) {
5102      result.add(new BooleanType(true).noExtensions());
5103    } else if (focus.get(0) instanceof BooleanType) {
5104      result.add(new BooleanType(true).noExtensions());
5105    } else if (focus.get(0) instanceof DecimalType) {
5106      result.add(new BooleanType(true).noExtensions());
5107    } else if (focus.get(0) instanceof StringType) {
5108      result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions());
5109    } else {
5110      result.add(new BooleanType(false).noExtensions());
5111    } 
5112    return result;
5113  }
5114
5115  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5116    List<Base> result = new ArrayList<Base>();
5117    result.add(new IntegerType(focus.size()).noExtensions());
5118    return result;
5119  }
5120
5121  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5122    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
5123    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
5124
5125    List<Base> result = new ArrayList<Base>();
5126    for (int i = i1; i < focus.size(); i++) {
5127      result.add(focus.get(i));
5128    } 
5129    return result;
5130  }
5131
5132  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5133    List<Base> result = new ArrayList<Base>();
5134    for (int i = 1; i < focus.size(); i++) {
5135      result.add(focus.get(i));
5136    } 
5137    return result;
5138  }
5139
5140  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5141    List<Base> result = new ArrayList<Base>();
5142    if (focus.size() > 0) {
5143      result.add(focus.get(focus.size()-1));
5144    } 
5145    return result;
5146  }
5147
5148  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5149    List<Base> result = new ArrayList<Base>();
5150    if (focus.size() > 0) {
5151      result.add(focus.get(0));
5152    } 
5153    return result;
5154  }
5155
5156
5157        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5158    List<Base> result = new ArrayList<Base>();
5159    List<Base> pc = new ArrayList<Base>();
5160    for (Base item : focus) {
5161      pc.clear();
5162      pc.add(item);
5163      Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
5164      if (v == Equality.True) {
5165        result.add(item);
5166      } 
5167    }
5168    return result;
5169  }
5170
5171  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5172    List<Base> result = new ArrayList<Base>();
5173    List<Base> pc = new ArrayList<Base>();
5174    int i = 0;
5175    for (Base item : focus) {
5176      pc.clear();
5177      pc.add(item);
5178      result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true));
5179      i++;
5180    }
5181    return result;
5182  }
5183
5184
5185        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5186    List<Base> result = new ArrayList<Base>();
5187    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
5188    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) {
5189      result.add(focus.get(Integer.parseInt(s)));
5190    } 
5191    return result;
5192  }
5193
5194  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5195    List<Base> result = new ArrayList<Base>();
5196                result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions());
5197    return result;
5198  }
5199
5200  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
5201    List<Base> result = new ArrayList<Base>();  
5202    Equality v = asBool(focus, exp);
5203    if (v != Equality.Null) {
5204      result.add(new BooleanType(v != Equality.True));
5205    } 
5206    return result;
5207  }
5208
5209  public class ElementDefinitionMatch {
5210    private ElementDefinition definition;
5211    private String fixedType;
5212    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
5213      super();
5214      this.definition = definition;
5215      this.fixedType = fixedType;
5216    }
5217    public ElementDefinition getDefinition() {
5218      return definition;
5219    }
5220    public String getFixedType() {
5221      return fixedType;
5222    }
5223
5224  }
5225
5226  private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr) throws PathEngineException, DefinitionException {
5227    if (Utilities.noString(type)) {
5228      throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName");
5229    } 
5230    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) {
5231      return;
5232    } 
5233    if (type.startsWith(Constants.NS_SYSTEM_TYPE)) {
5234      return;
5235    } 
5236
5237    if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 
5238      getSimpleTypeChildTypesByName(name, result);
5239    } else if (type.equals(TypeDetails.FP_ClassInfo)) { 
5240      getClassInfoChildTypesByName(name, result);
5241    } else {
5242      String url = null;
5243      if (type.contains("#")) {
5244        url = type.substring(0, type.indexOf("#"));
5245      } else {
5246        url = type;
5247      }
5248      String tail = "";
5249      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
5250      if (sd == null) {
5251        throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName");
5252      }
5253      List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
5254      ElementDefinitionMatch m = null;
5255      if (type.contains("#"))
5256        m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr);
5257      if (m != null && hasDataType(m.definition)) {
5258        if (m.fixedType != null)  {
5259          StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs()));
5260          if (dt == null) {
5261            throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs()), "getChildTypesByName");
5262          }
5263          sdl.add(dt);
5264        } else
5265          for (TypeRefComponent t : m.definition.getType()) {
5266            StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs()));
5267            if (dt == null) {
5268              throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs()), "getChildTypesByName");
5269            }
5270            sdl.add(dt);
5271          }
5272      } else {
5273        sdl.add(sd);
5274        if (type.contains("#")) {
5275          tail = type.substring(type.indexOf("#")+1);
5276          tail = tail.substring(tail.indexOf("."));
5277        }
5278      }
5279
5280      for (StructureDefinition sdi : sdl) {
5281        String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
5282        if (name.equals("**")) {
5283          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
5284          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
5285            if (ed.getPath().startsWith(path))
5286              for (TypeRefComponent t : ed.getType()) {
5287                if (t.hasCode() && t.getCodeElement().hasValue()) {
5288                  String tn = null;
5289                  if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
5290                    tn = sdi.getType()+"#"+ed.getPath();
5291                  } else {
5292                    tn = t.getCode();
5293                  }
5294                  if (t.getCode().equals("Resource")) {
5295                    for (String rn : worker.getResourceNames()) {
5296                      if (!result.hasType(worker, rn)) {
5297                        getChildTypesByName(result.addType(rn), "**", result, expr);
5298                      }                  
5299                    }
5300                  } else if (!result.hasType(worker, tn)) {
5301                    getChildTypesByName(result.addType(tn), "**", result, expr);
5302                  }
5303                }
5304              }
5305          }      
5306        } else if (name.equals("*")) {
5307          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
5308          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
5309            if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
5310              for (TypeRefComponent t : ed.getType()) {
5311                if (Utilities.noString(t.getCode())) { // Element.id or Extension.url
5312                  result.addType("System.string");
5313                } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
5314                  result.addType(sdi.getType()+"#"+ed.getPath());
5315                } else if (t.getCode().equals("Resource")) {
5316                  result.addTypes(worker.getResourceNames());
5317                } else {
5318                  result.addType(t.getCode());
5319                }
5320              }
5321          }
5322        } else {
5323          path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
5324
5325          ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr);
5326          if (ed != null) {
5327            if (!Utilities.noString(ed.getFixedType()))
5328              result.addType(ed.getFixedType());
5329            else {
5330              for (TypeRefComponent t : ed.getDefinition().getType()) {
5331                if (Utilities.noString(t.getCode())) {
5332                  if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { 
5333                    result.addType(TypeDetails.FP_NS, "string");
5334                  }
5335                  break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
5336                }
5337
5338                ProfiledType pt = null;
5339                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
5340                  pt = new ProfiledType(sdi.getUrl()+"#"+path);
5341                } else if (t.getCode().equals("Resource")) {
5342                  result.addTypes(worker.getResourceNames());
5343                } else {
5344                  pt = new ProfiledType(t.getCode());
5345                }
5346                if (pt != null) {
5347                  if (t.hasProfile()) {
5348                    pt.addProfiles(t.getProfile());
5349                  }
5350                  if (ed.getDefinition().hasBinding()) {
5351                    pt.addBinding(ed.getDefinition().getBinding());
5352                  }
5353                  result.addType(pt);
5354                }
5355              }
5356            }
5357          }
5358        }
5359      }
5360    }
5361  }
5362
5363  private void getClassInfoChildTypesByName(String name, TypeDetails result) {
5364    if (name.equals("namespace")) {
5365      result.addType(TypeDetails.FP_String);
5366    }
5367    if (name.equals("name")) {
5368      result.addType(TypeDetails.FP_String);
5369    }
5370  }
5371
5372
5373  private void getSimpleTypeChildTypesByName(String name, TypeDetails result) {
5374    if (name.equals("namespace")) {
5375      result.addType(TypeDetails.FP_String);
5376    }
5377    if (name.equals("name")) {
5378      result.addType(TypeDetails.FP_String);
5379    }
5380  }
5381
5382
5383  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
5384    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
5385      if (ed.getPath().equals(path)) {
5386        if (ed.hasContentReference()) {
5387          return getElementDefinitionById(sd, ed.getContentReference());
5388        } else {
5389          return new ElementDefinitionMatch(ed, null);
5390        }
5391      }
5392      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) {
5393        return new ElementDefinitionMatch(ed, null);
5394      }
5395      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
5396        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
5397        if (primitiveTypes.contains(s)) {
5398          return new ElementDefinitionMatch(ed, s);
5399        } else {
5400          return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
5401        }
5402      }
5403      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 
5404        // now we walk into the type.
5405        if (ed.getType().size() > 1) { // if there's more than one type, the test above would fail this
5406          throw new Error("Internal typing issue....");
5407        }
5408        StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs()));
5409        if (nsd == null) { 
5410          throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition");
5411        }
5412        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr);
5413      }
5414      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
5415        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
5416        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr);
5417      }
5418    }
5419    return null;
5420  }
5421
5422  private boolean isAbstractType(List<TypeRefComponent> list) {
5423    return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
5424  }
5425
5426
5427  private boolean hasType(ElementDefinition ed, String s) {
5428    for (TypeRefComponent t : ed.getType()) {
5429      if (s.equalsIgnoreCase(t.getCode())) {
5430        return true;
5431      }
5432    }
5433    return false;
5434  }
5435
5436  private boolean hasDataType(ElementDefinition ed) {
5437    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
5438  }
5439
5440  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
5441    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
5442      if (ref.equals("#"+ed.getId())) {
5443        return new ElementDefinitionMatch(ed, null);
5444      }
5445    }
5446    return null;
5447  }
5448
5449
5450  public boolean hasLog() {
5451    return log != null && log.length() > 0;
5452  }
5453
5454
5455  public String takeLog() {
5456    if (!hasLog()) {
5457      return "";
5458    }
5459    String s = log.toString();
5460    log = new StringBuilder();
5461    return s;
5462  }
5463
5464
5465  /** given an element definition in a profile, what element contains the differentiating fixed 
5466   * for the element, given the differentiating expresssion. The expression is only allowed to 
5467   * use a subset of FHIRPath
5468   * 
5469   * @param profile
5470   * @param element
5471   * @return
5472   * @throws PathEngineException 
5473   * @throws DefinitionException 
5474   */
5475  public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source) throws DefinitionException {
5476    StructureDefinition sd = profile;
5477    TypedElementDefinition focus = null;
5478    boolean okToNotResolve = false;
5479
5480    if (expr.getKind() == Kind.Name) {
5481      if (element.getElement().hasSlicing()) {
5482        ElementDefinition slice = pickMandatorySlice(sd, element.getElement());
5483        if (slice == null) {
5484          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId());
5485        }
5486        element = new TypedElementDefinition(slice);
5487      }
5488
5489      if (expr.getName().equals("$this")) {
5490        focus = element;
5491      } else { 
5492        List<ElementDefinition> childDefinitions;
5493        childDefinitions = profileUtilities.getChildMap(sd, element.getElement());
5494        // if that's empty, get the children of the type
5495        if (childDefinitions.isEmpty()) {
5496
5497          sd = fetchStructureByType(element, expr);
5498          if (sd == null) {
5499            throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_THIS_CANNOT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId());
5500          }
5501          childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
5502        }
5503        for (ElementDefinition t : childDefinitions) {
5504          if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) 
5505            focus = new TypedElementDefinition(t);
5506            break;
5507          }
5508        }
5509      }
5510    } else if (expr.getKind() == Kind.Function) {
5511      if ("resolve".equals(expr.getName())) {
5512        if (element.getTypes().size() == 0) {
5513          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId());
5514        }
5515        if (element.getTypes().size() > 1) {
5516          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId());
5517        }
5518        if (!element.getTypes().get(0).hasTarget()) {
5519          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")");
5520        }
5521        if (element.getTypes().get(0).getTargetProfile().size() > 1) {
5522          throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId());
5523        }
5524        sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue());
5525        if (sd == null) {
5526          throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId());
5527        }
5528        focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep());
5529      } else if ("extension".equals(expr.getName())) {
5530        String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue();
5531        List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(sd, element.getElement());
5532        for (ElementDefinition t : childDefinitions) {
5533          if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
5534            System.out.println("t: "+t.getId());
5535            StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ?
5536              null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue());
5537            while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) {
5538              exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition());
5539            }
5540            if (exsd != null && exsd.getUrl().equals(targetUrl)) {
5541              if (profileUtilities.getChildMap(sd, t).isEmpty()) {
5542                sd = exsd;
5543              }
5544              focus = new TypedElementDefinition(t);
5545              break;
5546            }
5547          }
5548        }
5549        if (focus == null) { 
5550          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl());
5551        }
5552      } else if ("ofType".equals(expr.getName())) {
5553        if (!element.getElement().hasType()) {
5554          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId());
5555        }
5556        List<String> atn = new ArrayList<>();
5557        for (TypeRefComponent tr : element.getTypes()) {
5558          if (!tr.hasCode()) {
5559            throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId());
5560          }
5561          atn.add(tr.getCode());
5562        }
5563        String stn = expr.getParameters().get(0).getName();  
5564        okToNotResolve = true;
5565        if ((atn.contains(stn))) {
5566          if (element.getTypes().size() > 1) {
5567            focus = new TypedElementDefinition(element.getElement(), stn);
5568          } else {
5569            focus = element;
5570          }
5571        }
5572      } else {
5573        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName());
5574      }
5575    } else if (expr.getKind() == Kind.Group) {
5576      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString());
5577    } else if (expr.getKind() == Kind.Constant) {
5578      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST);
5579    }
5580
5581    if (focus == null) { 
5582      if (okToNotResolve) {
5583        return null;
5584      } else {
5585        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl());
5586      }
5587    } else if (expr.getInner() == null) {
5588      return focus;
5589    } else {
5590      return evaluateDefinition(expr.getInner(), sd, focus, profile);
5591    }
5592  }
5593
5594  private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException {
5595    List<ElementDefinition> list = profileUtilities.getSliceList(sd, element);
5596    for (ElementDefinition ed : list) {
5597      if (ed.getMin() > 0) {
5598        return ed;
5599      }
5600    }
5601    return null;
5602  }
5603
5604
5605  private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException {
5606    if (ed.getTypes().size() == 0) {
5607      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId());
5608    }
5609    if (ed.getTypes().size() > 1) {
5610      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId());
5611    }
5612    if (ed.getTypes().get(0).getProfile().size() > 1) {
5613      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId());
5614    }
5615    if (ed.getTypes().get(0).hasProfile()) { 
5616      return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue());
5617    } else {
5618      return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), worker.getOverrideVersionNs()));
5619    }
5620  }
5621
5622
5623  private boolean tailMatches(ElementDefinition t, String d) {
5624    String tail = tailDot(t.getPath());
5625    if (d.contains("[")) {
5626      return tail.startsWith(d.substring(0, d.indexOf('[')));
5627    } else if (tail.equals(d)) {
5628      return true;
5629    } 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())) {
5630      return tail.startsWith(d);
5631    } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) {
5632      return true;
5633    }
5634    return false;
5635  }
5636
5637  private String tailDot(String path) {
5638    return path.substring(path.lastIndexOf(".") + 1);
5639  }
5640
5641  private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException {
5642    if (items.size() == 0) {
5643      return Equality.Null;
5644    } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) {
5645      return asBool(items.get(0), true);
5646    } else if (items.size() == 1) {
5647      return Equality.True; 
5648    } else {
5649      throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items));
5650    }
5651  }
5652  
5653  private Equality asBoolFromInt(String s) {
5654    try {
5655      int i = Integer.parseInt(s);
5656      switch (i) {
5657      case 0: return Equality.False;
5658      case 1: return Equality.True;
5659      default: return Equality.Null;
5660      }
5661    } catch (Exception e) {
5662      return Equality.Null;
5663    }
5664  }
5665
5666  private Equality asBoolFromDec(String s) {
5667    try {
5668      BigDecimal d = new BigDecimal(s);
5669      if (d.compareTo(BigDecimal.ZERO) == 0) { 
5670        return Equality.False;
5671      } else if (d.compareTo(BigDecimal.ONE) == 0) { 
5672        return Equality.True;
5673      } else {
5674        return Equality.Null;
5675      }
5676    } catch (Exception e) {
5677      return Equality.Null;
5678    }
5679  }
5680
5681  private Equality asBool(Base item, boolean narrow) {
5682    if (item instanceof BooleanType) { 
5683      return boolToTriState(((BooleanType) item).booleanValue());
5684    } else if (item.isBooleanPrimitive()) {
5685      if (Utilities.existsInList(item.primitiveValue(), "true")) {
5686        return Equality.True;
5687      } else if (Utilities.existsInList(item.primitiveValue(), "false")) {
5688        return Equality.False;
5689      } else { 
5690        return Equality.Null;
5691      }
5692    } else if (narrow) {
5693      return Equality.False;
5694    } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) {
5695      return asBoolFromInt(item.primitiveValue());
5696    } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) {
5697      return asBoolFromDec(item.primitiveValue());
5698    } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) {
5699      if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) {
5700        return Equality.True;
5701      } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) {
5702        return Equality.False;
5703      } else if (Utilities.isInteger(item.primitiveValue())) {
5704        return asBoolFromInt(item.primitiveValue());
5705      } else if (Utilities.isDecimal(item.primitiveValue(), true)) {
5706        return asBoolFromDec(item.primitiveValue());
5707      } else {
5708        return Equality.Null;
5709      }
5710    } 
5711    return Equality.Null;
5712  }
5713          
5714  private Equality boolToTriState(boolean b) {
5715    return b ? Equality.True : Equality.False;
5716  }
5717
5718
5719  public ValidationOptions getTerminologyServiceOptions() {
5720    return terminologyServiceOptions;
5721  }
5722
5723
5724  public IWorkerContext getWorker() {
5725    return worker;
5726  }
5727
5728  public boolean isAllowPolymorphicNames() {
5729    return allowPolymorphicNames;
5730  }
5731
5732  public void setAllowPolymorphicNames(boolean allowPolymorphicNames) {
5733    this.allowPolymorphicNames = allowPolymorphicNames;
5734  }
5735  
5736  
5737}