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