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