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