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