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