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