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