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() || left.get(0).hasType("date", "dateTime", "instant") || "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.hasType("date") && r.hasType("Quantity")) {
2752      DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 
2753      result.add(dateAdd(dl, (Quantity) r, false, expr));
2754    } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) {
2755      DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 
2756      result.add(dateAdd(dl, (Quantity) r, false, expr));
2757    } else {
2758      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "+", left.get(0).fhirType(), right.get(0).fhirType());
2759    }
2760    return result;
2761  }
2762
2763  private BaseDateTimeType dateAdd(BaseDateTimeType d, Quantity q, boolean negate, ExpressionNode holder) {
2764    BaseDateTimeType result = (BaseDateTimeType) d.copy();
2765
2766    int value = negate ? 0 - q.getValue().intValue() : q.getValue().intValue();
2767    switch (q.hasCode() ? q.getCode() : q.getUnit()) {
2768    case "years": 
2769    case "year": 
2770      result.add(Calendar.YEAR, value);
2771      break;
2772    case "a":
2773      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
2774    case "months": 
2775    case "month": 
2776      result.add(Calendar.MONTH, value);
2777      break;
2778    case "mo":
2779      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
2780    case "weeks": 
2781    case "week": 
2782    case "wk":
2783      result.add(Calendar.DAY_OF_MONTH, value * 7);
2784      break;
2785    case "days": 
2786    case "day": 
2787    case "d":
2788      result.add(Calendar.DAY_OF_MONTH, value);
2789      break;
2790    case "hours": 
2791    case "hour": 
2792    case "h":
2793      result.add(Calendar.HOUR, value);
2794      break;
2795    case "minutes": 
2796    case "minute": 
2797    case "min":
2798      result.add(Calendar.MINUTE, value);
2799      break;
2800    case "seconds": 
2801    case "second": 
2802    case "s":
2803      result.add(Calendar.SECOND, value);
2804      break;
2805    case "milliseconds": 
2806    case "millisecond": 
2807    case "ms": 
2808      result.add(Calendar.MILLISECOND, value);
2809      break;
2810    default:
2811      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString());
2812    }
2813    return result;
2814  }
2815
2816  private List<Base> opTimes(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2817    if (left.size() == 0 || right.size() == 0) {
2818      return new ArrayList<Base>();
2819    }
2820    if (left.size() > 1) {
2821      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "*");
2822    }
2823    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
2824      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "*", left.get(0).fhirType());
2825    }
2826    if (right.size() > 1) {
2827      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "*");
2828    }
2829    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
2830      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "*", right.get(0).fhirType());
2831    }
2832
2833    List<Base> result = new ArrayList<Base>();
2834    Base l = left.get(0);
2835    Base r = right.get(0);
2836
2837    if (l.hasType("integer") && r.hasType("integer")) { 
2838      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
2839    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2840      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
2841    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2842      Pair pl = qtyToPair((Quantity) l);
2843      Pair pr = qtyToPair((Quantity) r);
2844      Pair p;
2845      try {
2846        p = worker.getUcumService().multiply(pl, pr);
2847        result.add(pairToQty(p));
2848      } catch (UcumException e) {
2849        throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #TODO: i18n
2850      }
2851    } else {
2852      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType());
2853    }
2854    return result;
2855  }
2856
2857
2858  private List<Base> opConcatenate(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2859    if (left.size() > 1) {
2860      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "&");
2861    }
2862    if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING)) {
2863      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "&", left.get(0).fhirType());
2864    }
2865    if (right.size() > 1) {
2866      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "&");
2867    }
2868    if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING)) {
2869      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "&", right.get(0).fhirType());
2870    }
2871
2872    List<Base> result = new ArrayList<Base>();
2873    String l = left.size() == 0 ? "" : left.get(0).primitiveValue();
2874    String r = right.size() == 0 ? "" : right.get(0).primitiveValue();
2875    result.add(new StringType(l + r));
2876    return result;
2877  }
2878
2879  private List<Base> opUnion(List<Base> left, List<Base> right, ExpressionNode expr) {
2880    List<Base> result = new ArrayList<Base>();
2881    for (Base item : left) {
2882      if (!doContains(result, item)) {
2883        result.add(item);
2884      }
2885    }
2886    for (Base item : right) {
2887      if (!doContains(result, item)) {
2888        result.add(item);
2889      }
2890    }
2891    return result;
2892  }
2893
2894  private boolean doContains(List<Base> list, Base item) {
2895    for (Base test : list) {
2896      Boolean eq = doEquals(test, item);
2897      if (eq != null && eq == true) {
2898        return true;
2899      }
2900    }
2901    return false;
2902  }
2903
2904
2905  private List<Base> opAnd(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2906    Equality l = asBool(left, expr);
2907    Equality r = asBool(right, expr);
2908    switch (l) {
2909    case False: return makeBoolean(false);
2910    case Null:
2911      if (r == Equality.False) {
2912        return makeBoolean(false);
2913      } else {
2914        return makeNull();
2915      }
2916    case True:
2917      switch (r) {
2918      case False: return makeBoolean(false);
2919      case Null: return makeNull();
2920      case True: return makeBoolean(true);
2921      }
2922    }
2923    return makeNull();
2924  }
2925
2926  private boolean isBoolean(List<Base> list, boolean b) {
2927    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
2928  }
2929
2930  private List<Base> opOr(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2931    Equality l = asBool(left, expr);
2932    Equality r = asBool(right, expr);
2933    switch (l) {
2934    case True: return makeBoolean(true);
2935    case Null:
2936      if (r == Equality.True) {
2937        return makeBoolean(true);
2938      } else {
2939        return makeNull();
2940      }
2941    case False:
2942      switch (r) {
2943      case False: return makeBoolean(false);
2944      case Null: return makeNull();
2945      case True: return makeBoolean(true);
2946      }
2947    }
2948    return makeNull();
2949  }
2950
2951  private List<Base> opXor(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2952    Equality l = asBool(left, expr);
2953    Equality r = asBool(right, expr);
2954    switch (l) {
2955    case True: 
2956      switch (r) {
2957      case False: return makeBoolean(true);
2958      case True: return makeBoolean(false);
2959      case Null: return makeNull();
2960      }
2961    case Null:
2962      return makeNull();
2963    case False:
2964      switch (r) {
2965      case False: return makeBoolean(false);
2966      case True: return makeBoolean(true);
2967      case Null: return makeNull();
2968      }
2969    }
2970    return makeNull();
2971  }
2972
2973  private List<Base> opImplies(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2974    Equality eq = asBool(left, expr);
2975    if (eq == Equality.False) { 
2976      return makeBoolean(true);
2977    } else if (right.size() == 0) {
2978      return makeNull();
2979    } else switch (asBool(right, expr)) {
2980    case False: return eq == Equality.Null ? makeNull() : makeBoolean(false);
2981    case Null: return makeNull();
2982    case True: return makeBoolean(true);
2983    }
2984    return makeNull();
2985  }
2986
2987
2988  private List<Base> opMinus(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
2989    if (left.size() == 0 || right.size() == 0) { 
2990      return new ArrayList<Base>();
2991    }
2992    if (left.size() > 1) {
2993      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "-");
2994    }
2995    if (!left.get(0).isPrimitive() && !left.get(0).hasType("Quantity")) {
2996      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "-", left.get(0).fhirType());
2997    }
2998    if (right.size() > 1) {
2999      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "-");
3000    }
3001    if (!right.get(0).isPrimitive() &&  !((left.get(0).isDateTime() || left.get(0).hasType("date", "dateTime", "instant") || "0".equals(left.get(0).primitiveValue()) || left.get(0).hasType("Quantity")) && right.get(0).hasType("Quantity"))) {
3002      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "-", right.get(0).fhirType());
3003    }
3004
3005    List<Base> result = new ArrayList<Base>();
3006    Base l = left.get(0);
3007    Base r = right.get(0);
3008
3009    if (l.hasType("integer") && r.hasType("integer")) { 
3010      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
3011    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
3012      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
3013    } else if (l.hasType("decimal", "integer", "Quantity") && r.hasType("Quantity")) { 
3014      String s = l.primitiveValue();
3015      if ("0".equals(s)) {
3016        Quantity qty = (Quantity) r;
3017        result.add(qty.copy().setValue(qty.getValue().abs()));
3018      }
3019    } else if (l.hasType("date") && r.hasType("Quantity")) {
3020      DateType dl = l instanceof DateType ? (DateType) l : new DateType(l.primitiveValue()); 
3021      result.add(dateAdd(dl, (Quantity) r, true, expr));
3022    } else if ((l.isDateTime() || l.hasType("dateTime") || l.hasType("instant")) && r.hasType("Quantity")) {
3023      DateTimeType dl = l instanceof DateTimeType ? (DateTimeType) l : new DateTimeType(l.primitiveValue()); 
3024      result.add(dateAdd(dl, (Quantity) r, true, expr));
3025    } else {
3026      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "-", left.get(0).fhirType(), right.get(0).fhirType());
3027    }
3028    return result;
3029  }
3030
3031  private List<Base> opDivideBy(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3032    if (left.size() == 0 || right.size() == 0) {
3033      return new ArrayList<Base>();
3034    }
3035    if (left.size() > 1) {
3036      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "/");
3037    }
3038    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
3039      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "/", left.get(0).fhirType());
3040    }
3041    if (right.size() > 1) {
3042      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "/");
3043    }
3044    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
3045      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "/", right.get(0).fhirType());
3046    }
3047
3048    List<Base> result = new ArrayList<Base>();
3049    Base l = left.get(0);
3050    Base r = right.get(0);
3051
3052    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
3053      Decimal d1;
3054      try {
3055        d1 = new Decimal(l.primitiveValue());
3056        Decimal d2 = new Decimal(r.primitiveValue());
3057        result.add(new DecimalType(d1.divide(d2).asDecimal()));
3058      } catch (UcumException e) {
3059        // just return nothing
3060      }
3061    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
3062      Pair pl = qtyToPair((Quantity) l);
3063      Pair pr = qtyToPair((Quantity) r);
3064      Pair p;
3065      try {
3066        p = worker.getUcumService().divideBy(pl, pr);
3067        result.add(pairToQty(p));
3068      } catch (UcumException e) {
3069        // just return nothing
3070      }
3071    } else {
3072      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "/", left.get(0).fhirType(), right.get(0).fhirType());
3073    }
3074    return result;
3075  }
3076
3077  private List<Base> opDiv(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3078    if (left.size() == 0 || right.size() == 0) { 
3079      return new ArrayList<Base>();
3080    }
3081    if (left.size() > 1) {
3082      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "div");
3083    }
3084    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity)) {
3085      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "div", left.get(0).fhirType());
3086    }
3087    if (right.size() > 1) {
3088      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "div");
3089    }
3090    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity)) {
3091      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "div", right.get(0).fhirType());
3092    }
3093
3094    List<Base> result = new ArrayList<Base>();
3095    Base l = left.get(0);
3096    Base r = right.get(0);
3097
3098    if (l.hasType("integer") && r.hasType("integer")) {
3099      int divisor = Integer.parseInt(r.primitiveValue());
3100      if (divisor != 0) { 
3101        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / divisor));
3102      }
3103    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
3104      Decimal d1;
3105      try {
3106        d1 = new Decimal(l.primitiveValue());
3107        Decimal d2 = new Decimal(r.primitiveValue());
3108        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
3109      } catch (UcumException e) {
3110        // just return nothing
3111      }
3112    } else {
3113      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "div", left.get(0).fhirType(), right.get(0).fhirType());
3114    }
3115    return result;
3116  }
3117
3118  private List<Base> opMod(List<Base> left, List<Base> right, ExpressionNode expr) throws PathEngineException {
3119    if (left.size() == 0 || right.size() == 0) {
3120      return new ArrayList<Base>();
3121    } if (left.size() > 1) {
3122      throw makeExceptionPlural(left.size(), expr, I18nConstants.FHIRPATH_LEFT_VALUE, "mod");
3123    }
3124    if (!left.get(0).isPrimitive()) {
3125      throw makeException(expr, I18nConstants.FHIRPATH_LEFT_VALUE_WRONG_TYPE, "mod", left.get(0).fhirType());
3126    }
3127    if (right.size() > 1) {
3128      throw makeExceptionPlural(right.size(), expr, I18nConstants.FHIRPATH_RIGHT_VALUE, "mod");
3129    }
3130    if (!right.get(0).isPrimitive()) {
3131      throw makeException(expr, I18nConstants.FHIRPATH_RIGHT_VALUE_WRONG_TYPE, "mod", right.get(0).fhirType());
3132    }
3133
3134    List<Base> result = new ArrayList<Base>();
3135    Base l = left.get(0);
3136    Base r = right.get(0);
3137
3138    if (l.hasType("integer") && r.hasType("integer")) { 
3139      int modulus = Integer.parseInt(r.primitiveValue());
3140      if (modulus != 0) {
3141        result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % modulus));
3142      }
3143    } else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
3144      Decimal d1;
3145      try {
3146        d1 = new Decimal(l.primitiveValue());
3147        Decimal d2 = new Decimal(r.primitiveValue());
3148        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
3149      } catch (UcumException e) {
3150        throw new PathEngineException(e);
3151      }
3152    } else {
3153      throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "mod", left.get(0).fhirType(), right.get(0).fhirType());
3154    }
3155    return result;
3156  }
3157
3158
3159  private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
3160    if (constant instanceof BooleanType) { 
3161      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3162    } else if (constant instanceof IntegerType) {
3163      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3164    } else if (constant instanceof DecimalType) {
3165      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
3166    } else if (constant instanceof Quantity) {
3167      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
3168    } else if (constant instanceof FHIRConstant) {
3169      return resolveConstantType(context, ((FHIRConstant) constant).getValue(), expr, explicitConstant);
3170    } else if (constant == null) {
3171      return new TypeDetails(CollectionStatus.SINGLETON);      
3172    } else {
3173      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3174    }
3175  }
3176
3177  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s, ExpressionNode expr, boolean explicitConstant) throws PathEngineException {
3178    if (s.startsWith("@")) {
3179      if (s.startsWith("@T")) {
3180        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
3181      } else {
3182        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3183      }
3184    } else if (s.equals("%sct")) {
3185      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3186    } else if (s.equals("%loinc")) {
3187      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3188    } else if (s.equals("%ucum")) {
3189      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3190    } else if (s.equals("%resource")) {
3191      if (context.resource == null) {
3192        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%resource", "no focus resource");
3193      }
3194      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
3195    } else if (s.equals("%rootResource")) {
3196      if (context.resource == null) {
3197        throw makeException(expr, I18nConstants.FHIRPATH_CANNOT_USE, "%rootResource", "no focus rootResource");
3198      }
3199      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
3200    } else if (s.equals("%context")) {
3201      return context.context;
3202    } else if (s.equals("%map-codes")) {
3203      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3204    } else if (s.equals("%us-zip")) {
3205      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3206    } else if (s.startsWith("%`vs-")) {
3207      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3208    } else if (s.startsWith("%`cs-")) {
3209      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3210    } else if (s.startsWith("%`ext-")) {
3211      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3212    } else if (hostServices == null) {
3213      String varName = s.substring(1);
3214      if (context.hasDefinedVariable(varName))
3215        return context.getDefinedVariable(varName);
3216      throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
3217    } else {
3218      String varName = s.substring(1);
3219      if (context.hasDefinedVariable(varName))
3220        return context.getDefinedVariable(varName);
3221      TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
3222      if (v == null) {
3223        throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); 
3224      } else {
3225        return v;
3226      }
3227    }
3228  }
3229
3230  private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
3231    List<Base> result = new ArrayList<Base>(); 
3232    if (atEntry && context.appInfo != null && hostServices != null) {
3233      // we'll see if the name matches a constant known by the context.
3234      List<Base> temp = hostServices.resolveConstant(this, context.appInfo, exp.getName(), true, false);
3235      if (!temp.isEmpty()) {
3236        result.addAll(temp);
3237        return result;
3238      }
3239    }
3240    if (atEntry && exp.getName() != null && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
3241      StructureDefinition sd = worker.fetchTypeDefinition(item.fhirType());
3242      if (sd == null) {
3243        // logical model
3244        if (exp.getName().equals(item.fhirType())) {
3245          result.add(item);          
3246        }
3247      } else {
3248        while (sd != null) {
3249          if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) {  
3250            result.add(item);
3251            break;
3252          }
3253          sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
3254        }
3255      }
3256    } else {
3257      getChildrenByName(item, exp.getName(), result);
3258    }
3259    if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) {
3260      // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
3261      // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
3262      result.addAll(hostServices.resolveConstant(this, context.appInfo, exp.getName(), false, false));
3263    }
3264    return result;
3265  }     
3266
3267  private String getParent(String rn) {
3268    return null;
3269  }
3270
3271
3272  private TypeDetails executeContextType(ExecutionTypeContext context, String name, ExpressionNode expr, boolean explicitConstant) throws PathEngineException, DefinitionException {
3273    if (hostServices == null) {
3274      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "Context Reference");
3275    }
3276    return hostServices.resolveConstantType(this, context.appInfo, name, explicitConstant);
3277  }
3278
3279  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
3280    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up
3281      return new TypeDetails(CollectionStatus.SINGLETON, type);
3282    }
3283    TypeDetails result = new TypeDetails(focus.getCollectionStatus());
3284    getChildTypesByName(type, exp.getName(), result, exp, focus, elementDependencies);
3285    return result;
3286  }
3287
3288
3289  private boolean isAncestor(String wanted, String stated) {
3290    try {
3291      StructureDefinition sd = worker.fetchTypeDefinition(wanted);
3292      while (sd != null) {
3293        if (stated.equals(sd.getTypeName())) {
3294          return true;
3295        }
3296        sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3297      }
3298      return false;
3299    } catch (Exception e) { 
3300      return false;
3301    }
3302  }
3303
3304  private String hashTail(String type) {
3305    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
3306  }
3307
3308
3309  private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, List<TypeDetails> paramTypes, boolean canBeNone) {
3310    int i = 0;
3311    for (ExpressionNode expr : exp.getParameters()) {
3312      if (isExpressionParameter(exp, i)) {
3313        paramTypes.add(executeType(changeThis(context, focus), focus, expr, elementDependencies, true, canBeNone, expr));
3314      } else {
3315        paramTypes.add(executeType(context, context.thisItem, expr, elementDependencies, true, canBeNone, expr));
3316      }
3317      i++;
3318    }
3319  }
3320
3321  @SuppressWarnings("unchecked")
3322  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies,  ExpressionNode container) throws PathEngineException, DefinitionException {
3323    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
3324    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) {
3325      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3326    } else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) {
3327      TypeDetails base = TypeDetails.empty();
3328      TypeDetails lFocus = focus;
3329      boolean changed = false;
3330      do {
3331        evaluateParameters(context, lFocus, exp, elementDependencies, paramTypes, true);
3332        changed = false;
3333        if (!base.contains(paramTypes.get(0))) {
3334          changed = true;
3335          base.addTypes(paramTypes.get(0));
3336          lFocus = base;
3337        }
3338      } while (changed);
3339      paramTypes.clear();
3340      paramTypes.add(base);
3341    } else if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Exists || 
3342        exp.getFunction() == Function.All || exp.getFunction() == Function.AllTrue || exp.getFunction() == Function.AnyTrue 
3343        || exp.getFunction() == Function.AllFalse || exp.getFunction() == Function.AnyFalse) {
3344      evaluateParameters(context, focus.toSingleton(), exp, elementDependencies, paramTypes, false);
3345    } else {
3346      evaluateParameters(context, focus, exp, elementDependencies, paramTypes, false);
3347    }
3348    if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) {
3349      if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) {
3350        typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION));
3351
3352      }
3353    }
3354    switch (exp.getFunction()) {
3355    case Empty : 
3356      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3357    case Not : 
3358      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3359    case Exists : { 
3360      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3361      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3362    }
3363    case SubsetOf : {
3364      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered()); 
3365      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3366    }
3367    case SupersetOf : {
3368      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus); 
3369      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3370    }
3371    case IsDistinct : 
3372      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3373    case Distinct : 
3374      return focus;
3375    case Count : 
3376      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3377    case Where : 
3378      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3379      // special case: where the focus is Reference, and the parameter to where is resolve() "is", we will suck up the target types
3380      if (focus.hasType("Reference")) {
3381        boolean canRestrictTargets = !exp.getParameters().isEmpty();
3382        List<String> targets = new ArrayList<>();
3383        if (canRestrictTargets) {
3384          ExpressionNode p = exp.getParameters().get(0);
3385          if (p.getKind() == Kind.Function && p.getName().equals("resolve") && p.getOperation() == Operation.Is) {
3386            targets.add(p.getOpNext().getName());
3387          } else {
3388            canRestrictTargets = false;
3389          }
3390        }
3391        if (canRestrictTargets) {
3392          TypeDetails td = focus.copy();
3393          td.getTargets().clear();
3394          td.getTargets().addAll(targets);
3395          return td;
3396        } else {
3397          return focus;
3398        }
3399      } else {
3400        return focus;
3401      }
3402    case Select : 
3403      return paramTypes.get(0);
3404    case All : 
3405      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean)); 
3406      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3407    case Repeat : 
3408      return paramTypes.get(0); 
3409    case Aggregate : 
3410      return anything(focus.getCollectionStatus());
3411    case Item : {
3412      checkOrdered(focus, "item", exp);
3413      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3414      return focus; 
3415    }
3416    case As : {
3417      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3418      String tn = checkType(focus, exp);
3419      TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
3420      if (td.typesHaveTargets()) {
3421        td.addTargets(focus.getTargets());
3422      }
3423      return td;
3424    }
3425    case OfType : { 
3426      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3427      String tn = checkType(focus, exp);
3428      TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
3429      if (td.typesHaveTargets()) {
3430        td.addTargets(focus.getTargets());
3431      }
3432      return td;
3433    }
3434    case Type : { 
3435      boolean s = false;
3436      boolean c = false;
3437      for (ProfiledType pt : focus.getProfiledTypes()) {
3438        s = s || pt.isSystemType();
3439        c = c || !pt.isSystemType();
3440      }
3441      if (s && c) {
3442        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo);
3443      } else if (s) {
3444        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo);
3445      } else {
3446        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo);
3447      }
3448    }
3449    case Is : {
3450      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3451      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3452    }
3453    case Single :
3454      return focus.toSingleton();
3455    case First : {
3456      checkOrdered(focus, "first", exp);
3457      return focus.toSingleton();
3458    }
3459    case Last : {
3460      checkOrdered(focus, "last", exp);
3461      return focus.toSingleton();
3462    }
3463    case Tail : {
3464      checkOrdered(focus, "tail", exp);
3465      return focus;
3466    }
3467    case Skip : {
3468      checkOrdered(focus, "skip", exp);
3469      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3470      return focus;
3471    }
3472    case Take : {
3473      checkOrdered(focus, "take", exp);
3474      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3475      return focus;
3476    }
3477    case Union : {
3478      return focus.union(paramTypes.get(0));
3479    }
3480    case Combine : {
3481      return focus.union(paramTypes.get(0));
3482    }
3483    case Intersect : {
3484      return focus.intersect(paramTypes.get(0));
3485    }
3486    case Exclude : {
3487      return focus;
3488    }
3489    case Iif : {
3490      TypeDetails types = new TypeDetails(null);
3491      checkSingleton(focus, "iif", exp);       
3492      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean));       
3493      types.update(paramTypes.get(1));
3494      if (paramTypes.size() > 2) {
3495        types.update(paramTypes.get(2));
3496      }
3497      return types;
3498    }
3499    case Lower : {
3500      checkContextString(focus, "lower", exp, true);
3501      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3502    }
3503    case Upper : {
3504      checkContextString(focus, "upper", exp, true);
3505      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3506    }
3507    case ToChars : {
3508      checkContextString(focus, "toChars", exp, true);
3509      return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 
3510    }
3511    case IndexOf : {
3512      checkContextString(focus, "indexOf", exp, true);
3513      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3514      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); 
3515    }
3516    case Substring : {
3517      checkContextString(focus, "subString", exp, true);
3518      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
3519      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3520    }
3521    case StartsWith : {
3522      checkContextString(focus, "startsWith", exp, true);
3523      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3524      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3525    }
3526    case EndsWith : {
3527      checkContextString(focus, "endsWith", exp, true);
3528      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3529      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3530    }
3531    case Matches : {
3532      checkContextString(focus, "matches", exp, true);
3533      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3534      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3535    }
3536    case MatchesFull : {
3537      checkContextString(focus, "matches", exp, true);
3538      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3539      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
3540    }
3541    case ReplaceMatches : {
3542      checkContextString(focus, "replaceMatches", exp, true);
3543      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3544      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
3545    }
3546    case Contains : {
3547      checkContextString(focus, "contains", exp, true);
3548      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3549      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3550    }
3551    case Replace : {
3552      checkContextString(focus, "replace", exp, true);
3553      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3554      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3555    }
3556    case Length : { 
3557      checkContextPrimitive(focus, "length", false, exp);
3558      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3559    }
3560    case Children : 
3561      return childTypes(focus, "*", exp);
3562    case Descendants : 
3563      return childTypes(focus, "**", exp);
3564    case MemberOf : {
3565      checkContextCoded(focus, "memberOf", exp);
3566      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3567      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3568    }
3569    case Trace : {
3570      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3571      return focus; 
3572    }
3573    case DefineVariable : {
3574      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.UNORDERED, TypeDetails.FP_String)); 
3575      // set the type of the variable
3576      // Actually evaluate the value of the first parameter (to get the name of the variable if possible)
3577      // and if have that, set it into the context
3578      ExpressionNode p = exp.getParameters().get(0);
3579      if (p.getKind() == Kind.Constant && p.getConstant() != null) {
3580        String varName = exp.getParameters().get(0).getConstant().primitiveValue();
3581        if (varName != null) {
3582          if (paramTypes.size() > 1)
3583            context.setDefinedVariable(varName, paramTypes.get(1));
3584          else
3585            context.setDefinedVariable(varName, focus);
3586        }
3587      } else {
3588        // this variable is not a constant, so we can't analyze what name it could have
3589      }
3590      return focus; 
3591    }
3592    case Check : {
3593      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3594      return focus; 
3595    }
3596    case Today : 
3597      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3598    case Now : 
3599      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3600    case Resolve : {
3601      checkContextReference(focus, "resolve", exp);
3602      return new TypeDetails(focus.getCollectionStatus(), "Resource"); 
3603    }
3604    case Extension : {
3605      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
3606      ExpressionNode p = exp.getParameters().get(0);
3607      if (p.getKind() == Kind.Constant && p.getConstant() != null) {
3608        String url = exp.getParameters().get(0).getConstant().primitiveValue();
3609        ExtensionDefinition ed = findExtensionDefinition(focus, url);
3610        if (ed != null) {
3611          return new TypeDetails(CollectionStatus.ORDERED, new ProfiledType(ed.sd.getUrl()));
3612        } else {
3613          typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_UNKNOWN_EXTENSION, url), I18nConstants.FHIRPATH_UNKNOWN_EXTENSION));
3614        }
3615        return new TypeDetails(CollectionStatus.SINGLETON, "Extension");
3616      }
3617    }
3618    case AnyTrue: 
3619      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3620    case AllTrue: 
3621      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3622    case AnyFalse: 
3623      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3624    case AllFalse: 
3625      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3626    case HasValue : 
3627      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3628    case HtmlChecks1 : 
3629      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3630    case HtmlChecks2 : 
3631      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3632    case Comparable : 
3633      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3634    case Encode:
3635      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3636      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3637    case Decode:
3638      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3639      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3640    case Escape:
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 Unescape:
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 Trim:
3647      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3648    case Split:
3649      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3650      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3651    case Join:
3652      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3653      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3654    case ToInteger : {
3655      checkContextPrimitive(focus, "toInteger", true, exp);
3656      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
3657    }
3658    case ToDecimal : {
3659      checkContextPrimitive(focus, "toDecimal", true, exp);
3660      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
3661    }
3662    case ToString : {
3663      checkContextPrimitive(focus, "toString", true, exp);
3664      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
3665    }
3666    case ToQuantity : {
3667      checkContextPrimitive(focus, "toQuantity", true, exp);
3668      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
3669    }
3670    case ToBoolean : {
3671      checkContextPrimitive(focus, "toBoolean", false, exp);
3672      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3673    }
3674    case ToDateTime : {
3675      checkContextPrimitive(focus, "ToDateTime", false, exp);
3676      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
3677    }
3678    case ToTime : {
3679      checkContextPrimitive(focus, "ToTime", false, exp);
3680      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
3681    }
3682    case ConvertsToString : 
3683    case ConvertsToQuantity :{
3684      checkContextPrimitive(focus, exp.getFunction().toCode(), true, exp);
3685      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3686    } 
3687    case ConvertsToInteger : 
3688    case ConvertsToDecimal : 
3689    case ConvertsToDateTime : 
3690    case ConvertsToDate : 
3691    case ConvertsToTime : 
3692    case ConvertsToBoolean : {
3693      checkContextPrimitive(focus, exp.getFunction().toCode(), false, exp);
3694      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3695    }
3696    case ConformsTo: {
3697      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3698      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);       
3699    }
3700    case Abs : {
3701      checkContextNumerical(focus, "abs", exp);
3702      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3703    }
3704    case Truncate :
3705    case Floor : 
3706    case Ceiling : {
3707      checkContextDecimal(focus, exp.getFunction().toCode(), exp);
3708      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);       
3709    }  
3710
3711    case Round :{
3712      checkContextDecimal(focus, "round", exp);
3713      if (paramTypes.size() > 0) {
3714        checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
3715      }
3716      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3717    } 
3718
3719    case Exp : 
3720    case Ln : 
3721    case Sqrt : {
3722      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3723      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3724    }
3725    case Log :  {
3726      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3727      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3728      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3729    }
3730    case Power : {
3731      checkContextNumerical(focus, exp.getFunction().toCode(), exp);      
3732      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_NUMBERS));
3733      return new TypeDetails(CollectionStatus.SINGLETON, focus.getTypes());       
3734    }
3735
3736    case LowBoundary:
3737    case HighBoundary: {
3738      checkContextContinuous(focus, exp.getFunction().toCode(), exp, true);      
3739      if (paramTypes.size() > 0) {
3740        checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
3741      }
3742      if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
3743        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);       
3744      } else if (focus.hasType("decimal") || focus.hasType("integer")) {
3745        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);       
3746      } else {
3747        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);       
3748      }
3749    }
3750    case Precision: {
3751      checkContextContinuous(focus, exp.getFunction().toCode(), exp, false);      
3752      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);       
3753    }
3754    case hasTemplateIdOf: {
3755      checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
3756      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
3757    }
3758    case Custom : {
3759      return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes);
3760    }
3761    default:
3762      break;
3763    }
3764    throw new Error("not Implemented yet");
3765  }
3766
3767  private ExtensionDefinition findExtensionDefinition(TypeDetails focus, String url) {
3768    if (Utilities.isAbsoluteUrl(url)) {
3769      StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
3770      if (sd == null) {
3771        return null;
3772      } else {
3773        return new ExtensionDefinition(true, sd, sd.getSnapshot().getElementFirstRep());
3774      }
3775    }
3776    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, focus.getType());
3777    if (sd != null) {
3778      for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3779        if (ed.hasFixed() && url.equals(ed.getFixed().primitiveValue())) {
3780          return new ExtensionDefinition(false, sd, ed);
3781        }
3782      }
3783    }
3784    return null;
3785  }
3786
3787  private String checkType(TypeDetails focus, ExpressionNode exp) {
3788    String tn;
3789    if (exp.getParameters().get(0).getInner() != null) {
3790      tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
3791    } else {
3792      tn = "FHIR."+exp.getParameters().get(0).getName();
3793    }
3794    if (tn.startsWith("System.")) {
3795      tn = tn.substring(7);
3796    } else if (tn.startsWith("FHIR.")) {
3797      tn = Utilities.pathURL(Constants.NS_FHIR_ROOT, "StructureDefinition", tn.substring(5));
3798    } else if (tn.startsWith("CDA.")) {
3799      tn = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4));
3800    }
3801    
3802    if (typeCastIsImpossible(focus, tn)) {
3803      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE));
3804    }
3805    return tn;
3806  }
3807
3808  private boolean typeCastIsImpossible(TypeDetails focus, String tn) {
3809    return !focus.hasType(tn);
3810  }
3811
3812  private boolean isExpressionParameter(ExpressionNode exp, int i) {
3813    switch (i) {
3814    case 0:
3815      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;
3816    case 1:
3817      return exp.getFunction() == Function.Trace || exp.getFunction() == Function.DefineVariable;
3818    default: 
3819      return false;
3820    }
3821  }
3822
3823
3824  private void checkParamTypes(ExpressionNode expr, String funcName,List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
3825    int i = 0;
3826    for (TypeDetails pt : typeSet) {
3827      if (i == paramTypes.size()) {
3828        return;
3829      }
3830      TypeDetails actual = paramTypes.get(i);
3831      i++;
3832      for (String a : actual.getTypes()) {
3833        if (!pt.hasType(worker, a)) {
3834          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, funcName, i, a, pt.toString());
3835        }
3836      }
3837      if (actual.getCollectionStatus() != CollectionStatus.SINGLETON && pt.getCollectionStatus() == CollectionStatus.SINGLETON) {
3838        typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER, funcName, i, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_PARAMETER));
3839      }
3840    }
3841  }
3842
3843  private void checkSingleton(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3844    if (focus.getCollectionStatus() != CollectionStatus.SINGLETON) {
3845      typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT, name, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_CONTEXT));
3846    }
3847  }
3848
3849  private void checkOrdered(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3850    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED) {
3851      throw makeException(expr, I18nConstants.FHIRPATH_ORDERED_ONLY, name);
3852    }
3853  }
3854
3855  private void checkContextReference(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3856    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical")) {
3857      throw makeException(expr, I18nConstants.FHIRPATH_REFERENCE_ONLY, name, focus.describe());
3858    }
3859  }
3860
3861
3862  private void checkContextCoded(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3863    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept")) {
3864      throw makeException(expr, I18nConstants.FHIRPATH_CODED_ONLY, name, focus.describe());
3865    }
3866  }
3867
3868
3869  private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException {
3870    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")) {
3871      throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe());
3872    }
3873  }
3874
3875
3876  private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty, ExpressionNode expr) throws PathEngineException {
3877    if (!focus.hasNoTypes()) {
3878      if (canQty) {
3879        if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity")) {
3880          throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), "Quantity, "+primitiveTypes.toString());
3881        }
3882      } else if (!focus.hasType(primitiveTypes)) {
3883        throw makeException(expr, I18nConstants.FHIRPATH_PRIMITIVE_ONLY, name, focus.describe(), primitiveTypes.toString());
3884      }
3885    }
3886  }
3887
3888  private void checkContextNumerical(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3889    if (!focus.hasNoTypes() && !focus.hasType("integer")  && !focus.hasType("decimal") && !focus.hasType("Quantity")) {
3890      throw makeException(expr, I18nConstants.FHIRPATH_NUMERICAL_ONLY, name, focus.describe());
3891    }    
3892  }
3893
3894  private void checkContextDecimal(TypeDetails focus, String name, ExpressionNode expr) throws PathEngineException {
3895    if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("integer")) {
3896      throw makeException(expr, I18nConstants.FHIRPATH_DECIMAL_ONLY, name, focus.describe());
3897    }    
3898  }
3899
3900  private void checkContextContinuous(TypeDetails focus, String name, ExpressionNode expr, boolean allowInteger) throws PathEngineException {
3901    if (!focus.hasNoTypes() && !focus.hasType("decimal") && !focus.hasType("date") && !focus.hasType("dateTime") && !focus.hasType("time") && !focus.hasType("Quantity") && !(allowInteger && focus.hasType("integer"))) {
3902      throw makeException(expr, I18nConstants.FHIRPATH_CONTINUOUS_ONLY, name, focus.describe());
3903    }    
3904  }
3905
3906  private TypeDetails childTypes(TypeDetails focus, String mask, ExpressionNode expr) throws PathEngineException, DefinitionException {
3907    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
3908    for (String f : focus.getTypes()) {
3909      getChildTypesByName(f, mask, result, expr, null, null);
3910    }
3911    return result;
3912  }
3913
3914  private TypeDetails anything(CollectionStatus status) {
3915    return new TypeDetails(status, allTypes.keySet());
3916  }
3917
3918  //    private boolean isPrimitiveType(String s) {
3919  //            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");
3920  //    }
3921
3922  private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3923    switch (exp.getFunction()) {
3924    case Empty : return funcEmpty(context, focus, exp);
3925    case Not : return funcNot(context, focus, exp);
3926    case Exists : return funcExists(context, focus, exp);
3927    case SubsetOf : return funcSubsetOf(context, focus, exp);
3928    case SupersetOf : return funcSupersetOf(context, focus, exp);
3929    case IsDistinct : return funcIsDistinct(context, focus, exp);
3930    case Distinct : return funcDistinct(context, focus, exp);
3931    case Count : return funcCount(context, focus, exp);
3932    case Where : return funcWhere(context, focus, exp);
3933    case Select : return funcSelect(context, focus, exp);
3934    case All : return funcAll(context, focus, exp);
3935    case Repeat : return funcRepeat(context, focus, exp);
3936    case Aggregate : return funcAggregate(context, focus, exp);
3937    case Item : return funcItem(context, focus, exp);
3938    case As : return funcAs(context, focus, exp);
3939    case OfType : return funcOfType(context, focus, exp);
3940    case Type : return funcType(context, focus, exp);
3941    case Is : return funcIs(context, focus, exp);
3942    case Single : return funcSingle(context, focus, exp);
3943    case First : return funcFirst(context, focus, exp);
3944    case Last : return funcLast(context, focus, exp);
3945    case Tail : return funcTail(context, focus, exp);
3946    case Skip : return funcSkip(context, focus, exp);
3947    case Take : return funcTake(context, focus, exp);
3948    case Union : return funcUnion(context, focus, exp);
3949    case Combine : return funcCombine(context, focus, exp);
3950    case Intersect : return funcIntersect(context, focus, exp);
3951    case Exclude : return funcExclude(context, focus, exp);
3952    case Iif : return funcIif(context, focus, exp);
3953    case Lower : return funcLower(context, focus, exp);
3954    case Upper : return funcUpper(context, focus, exp);
3955    case ToChars : return funcToChars(context, focus, exp);
3956    case IndexOf : return funcIndexOf(context, focus, exp);
3957    case Substring : return funcSubstring(context, focus, exp);
3958    case StartsWith : return funcStartsWith(context, focus, exp);
3959    case EndsWith : return funcEndsWith(context, focus, exp);
3960    case Matches : return funcMatches(context, focus, exp);
3961    case MatchesFull : return funcMatchesFull(context, focus, exp);
3962    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
3963    case Contains : return funcContains(context, focus, exp);
3964    case Replace : return funcReplace(context, focus, exp);
3965    case Length : return funcLength(context, focus, exp);
3966    case Children : return funcChildren(context, focus, exp);
3967    case Descendants : return funcDescendants(context, focus, exp);
3968    case MemberOf : return funcMemberOf(context, focus, exp);
3969    case Trace : return funcTrace(context, focus, exp);
3970    case DefineVariable : return funcDefineVariable(context, focus, exp);
3971    case Check : return funcCheck(context, focus, exp);
3972    case Today : return funcToday(context, focus, exp);
3973    case Now : return funcNow(context, focus, exp);
3974    case Resolve : return funcResolve(context, focus, exp);
3975    case Extension : return funcExtension(context, focus, exp);
3976    case AnyFalse: return funcAnyFalse(context, focus, exp);
3977    case AllFalse: return funcAllFalse(context, focus, exp);
3978    case AnyTrue: return funcAnyTrue(context, focus, exp);
3979    case AllTrue: return funcAllTrue(context, focus, exp);
3980    case HasValue : return funcHasValue(context, focus, exp);
3981    case Encode : return funcEncode(context, focus, exp);
3982    case Decode : return funcDecode(context, focus, exp);
3983    case Escape : return funcEscape(context, focus, exp);
3984    case Unescape : return funcUnescape(context, focus, exp);
3985    case Trim : return funcTrim(context, focus, exp);
3986    case Split : return funcSplit(context, focus, exp);
3987    case Join : return funcJoin(context, focus, exp); 
3988    case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp);
3989    case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp);
3990    case Comparable : return funcComparable(context, focus, exp);
3991    case ToInteger : return funcToInteger(context, focus, exp);
3992    case ToDecimal : return funcToDecimal(context, focus, exp);
3993    case ToString : return funcToString(context, focus, exp);
3994    case ToBoolean : return funcToBoolean(context, focus, exp);
3995    case ToQuantity : return funcToQuantity(context, focus, exp);
3996    case ToDateTime : return funcToDateTime(context, focus, exp);
3997    case ToTime : return funcToTime(context, focus, exp);
3998    case ConvertsToInteger : return funcIsInteger(context, focus, exp);
3999    case ConvertsToDecimal : return funcIsDecimal(context, focus, exp);
4000    case ConvertsToString : return funcIsString(context, focus, exp);
4001    case ConvertsToBoolean : return funcIsBoolean(context, focus, exp);
4002    case ConvertsToQuantity : return funcIsQuantity(context, focus, exp);
4003    case ConvertsToDateTime : return funcIsDateTime(context, focus, exp);
4004    case ConvertsToDate : return funcIsDate(context, focus, exp);
4005    case ConvertsToTime : return funcIsTime(context, focus, exp);
4006    case ConformsTo : return funcConformsTo(context, focus, exp);
4007    case Round : return funcRound(context, focus, exp); 
4008    case Sqrt : return funcSqrt(context, focus, exp); 
4009    case Abs : return funcAbs(context, focus, exp); 
4010    case Ceiling : return funcCeiling(context, focus, exp); 
4011    case Exp : return funcExp(context, focus, exp); 
4012    case Floor : return funcFloor(context, focus, exp); 
4013    case Ln : return funcLn(context, focus, exp); 
4014    case Log : return funcLog(context, focus, exp); 
4015    case Power : return funcPower(context, focus, exp); 
4016    case Truncate : return funcTruncate(context, focus, exp);
4017    case LowBoundary : return funcLowBoundary(context, focus, exp);
4018    case HighBoundary : return funcHighBoundary(context, focus, exp);
4019    case Precision : return funcPrecision(context, focus, exp);
4020    case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp);
4021
4022
4023    case Custom: { 
4024      List<List<Base>> params = new ArrayList<List<Base>>();
4025      if (hostServices.paramIsType( exp.getName(), 0)) {
4026        if (exp.getParameters().size() > 0) {
4027          String tn;
4028          if (exp.getParameters().get(0).getInner() != null) {
4029            tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
4030          } else {
4031            tn = "FHIR."+exp.getParameters().get(0).getName();
4032          }
4033          List<Base> p = new ArrayList<>();
4034          p.add(new CodeType(tn));
4035          params.add(p);
4036        }
4037      } else {
4038        for (ExpressionNode p : exp.getParameters()) {
4039          params.add(execute(context, focus, p, true));
4040        }
4041      }
4042      return hostServices.executeFunction(this, context.appInfo, focus, exp.getName(), params);
4043    }
4044    default:
4045      throw new Error("not Implemented yet");
4046    }
4047  }
4048
4049  private List<Base> funcHasTemplateIdOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4050    List<Base> result = new ArrayList<Base>();
4051    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
4052    String sw = convertToString(swb);
4053
4054    StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw);
4055    if (focus.size() == 1 && sd != null) {
4056      boolean found = false;
4057      for (Identifier id : sd.getIdentifier()) {
4058        if (id.getValue().startsWith("urn:hl7ii:")) {   
4059          String[] p = id.getValue().split("\\:");
4060          if (p.length == 4) {
4061            found = found || hasTemplateId(focus.get(0), p[2], p[3]);
4062          }
4063        } else if (id.getValue().startsWith("urn:oid:")) {
4064          found = found || hasTemplateId(focus.get(0), id.getValue().substring(8));          
4065        }
4066      }
4067      result.add(new BooleanType(found));
4068    }
4069    return result;
4070  }
4071
4072  private boolean hasTemplateId(Base base, String rv) {
4073    List<Base> templateIds = base.listChildrenByName("templateId");
4074    for (Base templateId : templateIds) {
4075      Base root = templateId.getChildValueByName("root");
4076      Base extension = templateId.getChildValueByName("extension");
4077      if (extension == null && root != null && rv.equals(root.primitiveValue())) {
4078        return true;
4079      }
4080    }    
4081    return false;
4082  }
4083
4084  private boolean hasTemplateId(Base base, String rv, String ev) {
4085    List<Base> templateIds = base.listChildrenByName("templateId");
4086    for (Base templateId : templateIds) {
4087      Base root = templateId.getChildValueByName("root");
4088      Base extension = templateId.getChildValueByName("extension");
4089      if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) {
4090        return true;
4091      }
4092    }    
4093    return false;
4094  }
4095  
4096  private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4097    if (focus.size() != 1) {
4098      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size());
4099    }
4100    Base base = focus.get(0);
4101    List<Base> result = new ArrayList<Base>();
4102    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4103      Double d = Double.parseDouble(base.primitiveValue());
4104      try {
4105        result.add(new DecimalType(Math.sqrt(d)));
4106      } catch (Exception e) {
4107        // just return nothing
4108      }
4109    } else {
4110      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
4111    }
4112    return result;
4113  }
4114
4115
4116  private List<Base> funcAbs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4117    if (focus.size() != 1) {
4118      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "abs", focus.size());
4119    }
4120    Base base = focus.get(0);
4121    List<Base> result = new ArrayList<Base>();
4122    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4123      Double d = Double.parseDouble(base.primitiveValue());
4124      try {
4125        result.add(new DecimalType(Math.abs(d)));
4126      } catch (Exception e) {
4127        // just return nothing
4128      }
4129    } else if (base.hasType("Quantity")) {
4130      Quantity qty = (Quantity) base;
4131      result.add(qty.copy().setValue(qty.getValue().abs()));
4132    } else {
4133      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "abs", "(focus)", base.fhirType(), "integer or decimal");
4134    }
4135    return result;
4136  }
4137
4138
4139  private List<Base> funcCeiling(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4140    if (focus.size() != 1) {
4141      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ceiling", focus.size());
4142    }
4143    Base base = focus.get(0);
4144    List<Base> result = new ArrayList<Base>();
4145    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4146      Double d = Double.parseDouble(base.primitiveValue());
4147      try {result.add(new IntegerType((int) Math.ceil(d)));
4148      } catch (Exception e) {
4149        // just return nothing
4150      }
4151    } else {
4152      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ceiling", "(focus)", base.fhirType(), "integer or decimal");
4153    }
4154    return result;
4155  }
4156
4157  private List<Base> funcFloor(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4158    if (focus.size() != 1) {
4159      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "floor", focus.size());
4160    }
4161    Base base = focus.get(0);
4162    List<Base> result = new ArrayList<Base>();
4163    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4164      Double d = Double.parseDouble(base.primitiveValue());
4165      try {
4166        result.add(new IntegerType((int) Math.floor(d)));
4167      } catch (Exception e) {
4168        // just return nothing
4169      }
4170    } else {
4171      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "floor", "(focus)", base.fhirType(), "integer or decimal");
4172    }
4173    return result;
4174  }
4175
4176
4177  private List<Base> funcExp(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4178    if (focus.size() == 0) {
4179      return new ArrayList<Base>();
4180    }
4181    if (focus.size() > 1) {
4182      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "exp", focus.size());
4183    }
4184    Base base = focus.get(0);
4185    List<Base> result = new ArrayList<Base>();
4186    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4187      Double d = Double.parseDouble(base.primitiveValue());
4188      try {
4189        result.add(new DecimalType(Math.exp(d)));
4190      } catch (Exception e) {
4191        // just return nothing
4192      }
4193
4194    } else {
4195      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "exp", "(focus)", base.fhirType(), "integer or decimal");
4196    }
4197    return result;  
4198  }
4199
4200
4201  private List<Base> funcLn(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4202    if (focus.size() != 1) {
4203      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "ln", focus.size());
4204    }
4205    Base base = focus.get(0);
4206    List<Base> result = new ArrayList<Base>();
4207    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4208      Double d = Double.parseDouble(base.primitiveValue());
4209      try {
4210        result.add(new DecimalType(Math.log(d)));
4211      } catch (Exception e) {
4212        // just return nothing
4213      }        
4214    } else {
4215      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "ln", "(focus)", base.fhirType(), "integer or decimal");
4216    }
4217    return result;
4218  }
4219
4220
4221  private List<Base> funcLog(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4222    if (focus.size() != 1) {
4223      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "log", focus.size());
4224    }
4225    Base base = focus.get(0);
4226    List<Base> result = new ArrayList<Base>();
4227    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4228      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4229      if (n1.size() != 1) {
4230        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "0", "Multiple Values", "integer or decimal");
4231      }
4232      Double e = Double.parseDouble(n1.get(0).primitiveValue());
4233      Double d = Double.parseDouble(base.primitiveValue());
4234      try {
4235        result.add(new DecimalType(customLog(e, d)));
4236      } catch (Exception ex) {
4237        // just return nothing
4238      }
4239    } else {
4240      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "log", "(focus)", base.fhirType(), "integer or decimal");
4241    }
4242    return result;
4243  }
4244
4245  private static double customLog(double base, double logNumber) {
4246    return Math.log(logNumber) / Math.log(base);
4247  }
4248
4249  private List<Base> funcPower(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4250    if (focus.size() != 1) {
4251      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "power", focus.size());
4252    }
4253    Base base = focus.get(0);
4254    List<Base> result = new ArrayList<Base>();
4255    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4256      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4257      if (n1.size() != 1) {
4258        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer or decimal");
4259      }
4260      Double e = Double.parseDouble(n1.get(0).primitiveValue());
4261      Double d = Double.parseDouble(base.primitiveValue());
4262      try {
4263        result.add(new DecimalType(Math.pow(d, e)));
4264      } catch (Exception ex) {
4265        // just return nothing
4266      }
4267    } else {
4268      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "(focus)", base.fhirType(), "integer or decimal");
4269    }
4270    return result;
4271  }
4272
4273  private List<Base> funcTruncate(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4274    if (focus.size() != 1) {
4275      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "truncate", focus.size());
4276    }
4277    Base base = focus.get(0);
4278    List<Base> result = new ArrayList<Base>();
4279    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4280      String s = base.primitiveValue();
4281      if (s.contains(".")) {
4282        s = s.substring(0, s.indexOf("."));
4283      }
4284      result.add(new IntegerType(s));
4285    } else {
4286      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "integer or decimal");
4287    }
4288    return result;
4289  }
4290
4291  private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4292    if (focus.size() == 0) {
4293      return makeNull();
4294    }
4295    if (focus.size() > 1) {
4296      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size());
4297    }
4298    Integer precision = null;
4299    if (expr.getParameters().size() > 0) {
4300      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4301      if (n1.size() != 1) {
4302        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
4303      }
4304      precision = Integer.parseInt(n1.get(0).primitiveValue());
4305    }
4306    
4307    Base base = focus.get(0);
4308    List<Base> result = new ArrayList<Base>();
4309    
4310    if (base.hasType("decimal")) {
4311      if (precision == null || (precision >= 0 && precision < 17)) {
4312        result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4313      }
4314    } else if (base.hasType("integer")) {
4315      if (precision == null || (precision >= 0 && precision < 17)) {
4316        result.add(new DecimalType(Utilities.lowBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4317      }
4318    } else if (base.hasType("date")) {
4319      result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision)));
4320    } else if (base.hasType("dateTime")) {
4321      result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision)));
4322    } else if (base.hasType("time")) {
4323      result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision)));
4324    } else if (base.hasType("Quantity")) {
4325      String value = getNamedValue(base, "value");
4326      Base v = base.copy();
4327      v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == null ? 8 : precision)));
4328      result.add(v);
4329    } else {
4330      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4331    }
4332    return result;
4333  }
4334  
4335  private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4336    if (focus.size() == 0) {
4337      return makeNull();
4338    }
4339    if (focus.size() > 1) {
4340      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size());
4341    }
4342    Integer precision = null;
4343    if (expr.getParameters().size() > 0) {
4344      List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4345      if (n1.size() != 1) {
4346        throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "lowBoundary", "0", "Multiple Values", "integer");
4347      }
4348      precision = Integer.parseInt(n1.get(0).primitiveValue());
4349    }
4350    
4351    
4352    Base base = focus.get(0);
4353    List<Base> result = new ArrayList<Base>();
4354    if (base.hasType("decimal")) {
4355      if (precision == null || (precision >= 0 && precision < 17)) {
4356        result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4357      }
4358    } else if (base.hasType("integer")) {
4359      if (precision == null || (precision >= 0 && precision < 17)) {
4360        result.add(new DecimalType(Utilities.highBoundaryForDecimal(base.primitiveValue(), precision == null ? 8 : precision)));
4361      }
4362    } else if (base.hasType("date")) {
4363      result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 10 : precision)));
4364    } else if (base.hasType("dateTime")) {
4365      result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == null ? 17 : precision)));
4366    } else if (base.hasType("time")) {
4367      result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == null ? 9 : precision)));
4368    } else if (base.hasType("Quantity")) {
4369      String value = getNamedValue(base, "value");
4370      Base v = base.copy();
4371      v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == null ? 8 : precision)));
4372      result.add(v);
4373    } else {
4374      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4375    }
4376    return result;
4377  }
4378  
4379  private List<Base> funcPrecision(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4380    if (focus.size() != 1) {
4381      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size());
4382    }
4383    Base base = focus.get(0);
4384    List<Base> result = new ArrayList<Base>();
4385    if (base.hasType("decimal")) {
4386      result.add(new IntegerType(Utilities.getDecimalPrecision(base.primitiveValue())));
4387    } else if (base.hasType("date") || base.hasType("dateTime")) {
4388      result.add(new IntegerType(Utilities.getDatePrecision(base.primitiveValue())));
4389    } else if (base.hasType("time")) {
4390      result.add(new IntegerType(Utilities.getTimePrecision(base.primitiveValue())));
4391    } else {
4392      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
4393    }
4394    return result;
4395  }
4396
4397  private List<Base> funcRound(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4398    if (focus.size() != 1) {
4399      throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "round", focus.size());
4400    }
4401    Base base = focus.get(0);
4402    List<Base> result = new ArrayList<Base>();
4403    if (base.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
4404      int i = 0;
4405      if (expr.getParameters().size() == 1) {
4406        List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
4407        if (n1.size() != 1) {
4408          throw makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "power", "0", "Multiple Values", "integer");
4409        }
4410        i = Integer.parseInt(n1.get(0).primitiveValue());
4411      }
4412      BigDecimal  d = new BigDecimal (base.primitiveValue());
4413      result.add(new DecimalType(d.setScale(i, RoundingMode.HALF_UP)));
4414    } else {
4415      makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "round", "(focus)", base.fhirType(), "integer or decimal");
4416    }
4417    return result;
4418  }
4419
4420  private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
4421  private ContextUtilities cu;
4422  public static String bytesToHex(byte[] bytes) {
4423    char[] hexChars = new char[bytes.length * 2];
4424    for (int j = 0; j < bytes.length; j++) {
4425      int v = bytes[j] & 0xFF;
4426      hexChars[j * 2] = HEX_ARRAY[v >>> 4];
4427      hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
4428    }
4429    return new String(hexChars);
4430  }
4431
4432  public static byte[] hexStringToByteArray(String s) {
4433    int len = s.length();
4434    byte[] data = new byte[len / 2];
4435    for (int i = 0; i < len; i += 2) {
4436      data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16));
4437    }
4438    return data;
4439  }
4440
4441  private List<Base> funcEncode(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4442    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4443    String param = nl.get(0).primitiveValue();
4444
4445    List<Base> result = new ArrayList<Base>();
4446
4447    if (focus.size() == 1) {
4448      String cnt = focus.get(0).primitiveValue();
4449      if ("hex".equals(param)) {
4450        result.add(new StringType(bytesToHex(cnt.getBytes())));        
4451      } else if ("base64".equals(param)) {
4452        Base64.Encoder enc = Base64.getEncoder();
4453        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
4454      } else if ("urlbase64".equals(param)) {
4455        Base64.Encoder enc = Base64.getUrlEncoder();
4456        result.add(new StringType(enc.encodeToString(cnt.getBytes())));
4457      }
4458    }
4459    return result;      
4460  }
4461
4462  private List<Base> funcDecode(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 ("hex".equals(param)) {
4470        result.add(new StringType(new String(hexStringToByteArray(cnt))));        
4471      } else if ("base64".equals(param)) {
4472        Base64.Decoder enc = Base64.getDecoder();
4473        result.add(new StringType(new String(enc.decode(cnt))));
4474      } else if ("urlbase64".equals(param)) {
4475        Base64.Decoder enc = Base64.getUrlDecoder();
4476        result.add(new StringType(new String(enc.decode(cnt))));
4477      }
4478    }
4479    return result;  
4480  }
4481
4482  private List<Base> funcEscape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4483    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4484    String param = nl.get(0).primitiveValue();
4485
4486    List<Base> result = new ArrayList<Base>();
4487    if (focus.size() == 1) {
4488      String cnt = focus.get(0).primitiveValue();
4489      if ("html".equals(param)) {
4490        result.add(new StringType(Utilities.escapeXml(cnt)));        
4491      } else if ("json".equals(param)) {
4492        result.add(new StringType(Utilities.escapeJson(cnt)));        
4493      } else if ("url".equals(param)) {
4494        result.add(new StringType(Utilities.URLEncode(cnt)));        
4495      } else if ("md".equals(param)) {
4496        result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt)));        
4497      }
4498    }
4499
4500    return result;  
4501  }
4502
4503  private List<Base> funcUnescape(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4504    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4505    String param = nl.get(0).primitiveValue();
4506
4507    List<Base> result = new ArrayList<Base>();
4508    if (focus.size() == 1) {
4509      String cnt = focus.get(0).primitiveValue();
4510      if ("html".equals(param)) {
4511        result.add(new StringType(Utilities.unescapeXml(cnt)));        
4512      } else if ("json".equals(param)) {
4513        result.add(new StringType(Utilities.unescapeJson(cnt)));        
4514      } else if ("url".equals(param)) {
4515        result.add(new StringType(Utilities.URLDecode(cnt)));        
4516      } else if ("md".equals(param)) {
4517        result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt)));        
4518      }
4519    }
4520
4521    return result;  
4522  }
4523
4524  private List<Base> funcTrim(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4525    List<Base> result = new ArrayList<Base>();
4526    if (focus.size() == 1) {
4527      String cnt = focus.get(0).primitiveValue();
4528      result.add(new StringType(cnt.trim()));
4529    }
4530    return result;  
4531  }
4532
4533  private List<Base> funcSplit(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4534    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4535    String param = nl.get(0).primitiveValue();
4536
4537    List<Base> result = new ArrayList<Base>();
4538    if (focus.size() == 1) {
4539      String cnt = focus.get(0).primitiveValue();
4540      String[] sl = Utilities.simpleSplit(cnt, param);
4541      for (String s : sl) {
4542        result.add(new StringType(s));
4543      }
4544    }
4545    return result;  
4546  }
4547
4548  private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4549    List<Base> nl = exp.getParameters().size() > 0 ? execute(context, focus, exp.getParameters().get(0), true) : new ArrayList<Base>();
4550    String param = "";
4551    String param2 = "";
4552    if (exp.getParameters().size() > 0) {
4553      param = nl.get(0).primitiveValue();
4554      param2 = param;
4555      if (exp.getParameters().size() == 2) {
4556        nl = execute(context, focus, exp.getParameters().get(1), true);
4557        param2 = nl.get(0).primitiveValue();
4558      }
4559    }
4560    
4561    List<Base> result = new ArrayList<Base>();
4562    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2);
4563    for (Base i : focus) {
4564      b.append(i.primitiveValue());    
4565    }
4566    result.add(new StringType(b.toString()));
4567    return result;  
4568  }
4569
4570  private List<Base> funcHtmlChecks1(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4571    // todo: actually check the HTML
4572    if (focus.size() != 1) {
4573      return makeBoolean(false);          
4574    }
4575    XhtmlNode x = focus.get(0).getXhtml();
4576    if (x == null) {
4577      return makeBoolean(false);                
4578    }
4579    boolean ok = checkHtmlNames(x, true);
4580    if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) {
4581      ok = checkForContent(x);
4582    }
4583    return makeBoolean(ok);    
4584  }
4585
4586  private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4587    // todo: actually check the HTML
4588    if (focus.size() != 1) {
4589      return makeBoolean(false);          
4590    }
4591    XhtmlNode x = focus.get(0).getXhtml();
4592    if (x == null) {
4593      return makeBoolean(false);                
4594    }
4595    return makeBoolean(checkForContent(x));    
4596  }
4597
4598  private boolean checkForContent(XhtmlNode x) {
4599    if ((x.getNodeType() == NodeType.Text && !Utilities.noString(x.getContent().trim())) || (x.getNodeType() == NodeType.Element && "img".equals(x.getName()))) {
4600      return true;
4601    }
4602    for (XhtmlNode c : x.getChildNodes()) {
4603      if (checkForContent(c)) {
4604        return true;
4605      }
4606    }
4607    return false;
4608  }
4609
4610  private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4611    if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) {
4612      return makeBoolean(false);          
4613    }
4614    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4615    if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) {
4616      return makeBoolean(false);          
4617    }
4618    String s1 = getNamedValue(focus.get(0), "system");
4619    String u1 = getNamedValue(focus.get(0), "code");
4620    String s2 = getNamedValue(nl.get(0), "system");
4621    String u2 = getNamedValue(nl.get(0), "code");
4622    
4623    if (s1 == null || s2 == null || !s1.equals(s2)) {
4624      return makeBoolean(false);                
4625    }
4626    if (u1 == null || u2 == null) {
4627      return makeBoolean(false);                
4628    }
4629    if (u1.equals(u2)) {
4630      return makeBoolean(true);
4631    }
4632    if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) {
4633      try {
4634        return makeBoolean(worker.getUcumService().isComparable(u1, u2));
4635      } catch (UcumException e) {
4636        return makeBoolean(false);  
4637      }  
4638    } else {
4639      return makeBoolean(false);  
4640    }
4641  }
4642
4643
4644  private String getNamedValue(Base base, String name) {
4645    Property p = base.getChildByName(name);
4646    if (p.hasValues() && p.getValues().size() == 1) {
4647      return p.getValues().get(0).primitiveValue();
4648    }
4649    return null;
4650  }
4651
4652  private boolean checkHtmlNames(XhtmlNode node, boolean block) {
4653    if (node.getNodeType() == NodeType.Comment) {
4654      if (node.getContent().startsWith("DOCTYPE"))
4655        return false;
4656    }
4657    if (node.getNodeType() == NodeType.Element) {
4658      if (block) {
4659        if (!Utilities.existsInList(node.getName(),
4660            "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
4661            "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
4662            "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
4663            "code", "samp", "img", "map", "area")) {
4664          return false;
4665        }        
4666      } else {
4667        if (!Utilities.existsInList(node.getName(),
4668            "a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) {
4669          return false;
4670        }
4671      }
4672      for (String an : node.getAttributes().keySet()) {
4673        boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
4674            "title", "style", "class", "id", "idref", "lang", "xml:lang", "xml:space", "dir", "accesskey", "tabindex",
4675            // tables
4676            "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
4677
4678            Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
4679                "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
4680                "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
4681                "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
4682                "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
4683                );
4684        if (!ok) {
4685          return false;
4686        }
4687      }
4688      for (XhtmlNode c : node.getChildNodes()) {
4689        if (!checkHtmlNames(c, block && !"p".equals(c))) {
4690          return false;
4691        }
4692      }
4693    }
4694    return true;
4695  }
4696
4697  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4698    List<Base> result = new ArrayList<Base>();
4699    if (exp.getParameters().size() == 1) {
4700      List<Base> pc = new ArrayList<Base>();
4701      boolean all = true;
4702      for (Base item : focus) {
4703        pc.clear();
4704        pc.add(item);
4705        Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
4706        if (eq != Equality.True) {
4707          all = false;
4708          break;
4709        }
4710      }
4711      result.add(new BooleanType(all).noExtensions());
4712    } else {// (exp.getParameters().size() == 0) {
4713      boolean all = true;
4714      for (Base item : focus) {
4715        Equality eq = asBool(item, true);
4716        if (eq != Equality.True) {
4717          all = false;
4718          break;
4719        }
4720      }
4721      result.add(new BooleanType(all).noExtensions());
4722    }
4723    return result;
4724  }
4725
4726
4727  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
4728    ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, newThis);
4729    // append all of the defined variables from the context into the new context
4730    if (context.definedVariables != null) {
4731      for (String s : context.definedVariables.keySet()) {
4732        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4733      }
4734    }
4735    return newContext;
4736  }
4737
4738  private ExecutionContext contextForParameter(ExecutionContext context) {
4739    ExecutionContext newContext = new ExecutionContext(context.appInfo, context.focusResource, context.rootResource, context.context, context.thisItem);
4740    newContext.total = context.total;
4741    newContext.index = context.index;
4742    // append all of the defined variables from the context into the new context
4743    if (context.definedVariables != null) {
4744      for (String s : context.definedVariables.keySet()) {
4745        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4746      }
4747    }
4748    return newContext;
4749  }
4750
4751  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
4752    ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
4753    // append all of the defined variables from the context into the new context
4754    if (context.definedVariables != null) {
4755      for (String s : context.definedVariables.keySet()) {
4756        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4757      }
4758    }
4759    return newContext;
4760  }
4761
4762  private ExecutionTypeContext contextForParameter(ExecutionTypeContext context) {
4763    ExecutionTypeContext newContext = new ExecutionTypeContext(context.appInfo, context.resource, context.context, context.thisItem);
4764    // append all of the defined variables from the context into the new context
4765    if (context.definedVariables != null) {
4766      for (String s : context.definedVariables.keySet()) {
4767        newContext.setDefinedVariable(s, context.definedVariables.get(s));
4768      }
4769    }
4770    return newContext;
4771  }
4772
4773  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4774    List<Base> result = new ArrayList<Base>();
4775    result.add(DateTimeType.now());
4776    return result;
4777  }
4778
4779
4780  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4781    List<Base> result = new ArrayList<Base>();
4782    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
4783    return result;
4784  }
4785
4786
4787  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4788    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
4789    if (nl.size() != 1 || focus.size() != 1) {
4790      return new ArrayList<Base>();
4791    }
4792
4793    String url = nl.get(0).primitiveValue();
4794    ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url);
4795    if (vs == null) {
4796      return new ArrayList<Base>();
4797    }
4798    Base l = focus.get(0);
4799    if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
4800      return makeBoolean(worker.validateCode(terminologyServiceOptions.withGuessSystem(), TypeConvertor.castToCoding(l), vs).isOk());
4801    } else if (l.fhirType().equals("Coding")) {
4802      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk());
4803    } else if (l.fhirType().equals("CodeableConcept")) {
4804      return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk());
4805    } else {
4806      //      System.out.println("unknown type in funcMemberOf: "+l.fhirType());
4807      return new ArrayList<Base>();
4808    }
4809  }
4810
4811
4812  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4813    List<Base> result = new ArrayList<Base>();
4814    List<Base> current = new ArrayList<Base>();
4815    current.addAll(focus);
4816    List<Base> added = new ArrayList<Base>();
4817    boolean more = true;
4818    while (more) {
4819      added.clear();
4820      for (Base item : current) {
4821        getChildrenByName(item, "*", added);
4822      }
4823      more = !added.isEmpty();
4824      result.addAll(added);
4825      current.clear();
4826      current.addAll(added);
4827    }
4828    return result;
4829  }
4830
4831
4832  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4833    List<Base> result = new ArrayList<Base>();
4834    for (Base b : focus) {
4835      getChildrenByName(b, "*", result);
4836    }
4837    return result;
4838  }
4839
4840
4841  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException, PathEngineException {
4842    List<Base> result = new ArrayList<Base>();
4843    List<Base> tB = execute(context, focus, expr.getParameters().get(0), true);
4844    String t = convertToString(tB);
4845    List<Base> rB = execute(context, focus, expr.getParameters().get(1), true);
4846    String r = convertToString(rB);
4847
4848    if (focus.size() == 0 || tB.size() == 0 || rB.size() == 0) {
4849      //
4850    } else if (focus.size() == 1) {
4851      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4852        String f = convertToString(focus.get(0));
4853        if (Utilities.noString(f)) {
4854          result.add(new StringType(""));
4855        } else {
4856          String n = f.replace(t, r);
4857          result.add(new StringType(n));
4858        }
4859      }
4860    } else {
4861      throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "replace", focus.size());
4862    }
4863    return result;
4864  }
4865
4866
4867  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4868    List<Base> result = new ArrayList<Base>();
4869    List<Base> regexB = execute(context, focus, exp.getParameters().get(0), true);
4870    String regex = convertToString(regexB);
4871    List<Base> replB = execute(context, focus, exp.getParameters().get(1), true);
4872    String repl = convertToString(replB);
4873
4874    if (focus.size() == 0 || regexB.size() == 0 || replB.size() == 0) {
4875      //
4876    } else if (focus.size() == 1 && !Utilities.noString(regex)) {
4877      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4878        result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions());
4879      }
4880    } else {
4881      result.add(new StringType(convertToString(focus.get(0))).noExtensions());
4882    }
4883    return result;
4884  }
4885
4886
4887  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4888    List<Base> result = new ArrayList<Base>();
4889    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
4890    String sw = convertToString(swb);
4891
4892    if (focus.size() == 0) {
4893      //
4894    } else if (swb.size() == 0) {
4895      //
4896    } else if (Utilities.noString(sw)) {
4897      result.add(new BooleanType(true).noExtensions());
4898    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
4899      if (focus.size() == 1 && !Utilities.noString(sw)) {
4900        result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
4901      } else {
4902        result.add(new BooleanType(false).noExtensions());
4903      }
4904    }
4905    return result;
4906  }
4907
4908
4909  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4910    List<Base> result = new ArrayList<Base>();
4911    result.add(new StringType(convertToString(focus)).noExtensions());
4912    return result;
4913  }
4914
4915  private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4916    List<Base> result = new ArrayList<Base>();
4917    if (focus.size() == 1) {
4918      if (focus.get(0) instanceof BooleanType) {
4919        result.add(focus.get(0));
4920      } else if (focus.get(0) instanceof IntegerType) {
4921        int i = Integer.parseInt(focus.get(0).primitiveValue());
4922        if (i == 0) {
4923          result.add(new BooleanType(false).noExtensions());
4924        } else if (i == 1) {
4925          result.add(new BooleanType(true).noExtensions());
4926        }
4927      } else if (focus.get(0) instanceof DecimalType) {
4928        if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0) {
4929          result.add(new BooleanType(false).noExtensions());
4930        } else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0) {
4931          result.add(new BooleanType(true).noExtensions());
4932        }
4933      } else if (focus.get(0) instanceof StringType) {
4934        if ("true".equalsIgnoreCase(focus.get(0).primitiveValue())) {
4935          result.add(new BooleanType(true).noExtensions());
4936        } else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue())) {
4937          result.add(new BooleanType(false).noExtensions()); 
4938        }
4939      }
4940    }
4941    return result;
4942  }
4943
4944  private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
4945    List<Base> result = new ArrayList<Base>();
4946    if (focus.size() == 1) {
4947      if (focus.get(0) instanceof Quantity) {
4948        result.add(focus.get(0));
4949      } else if (focus.get(0) instanceof StringType) {
4950        Quantity q = parseQuantityString(focus.get(0).primitiveValue());
4951        if (q != null) {
4952          result.add(q.noExtensions());
4953        }
4954      } else if (focus.get(0) instanceof IntegerType) {
4955        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
4956      } else if (focus.get(0) instanceof DecimalType) {
4957        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
4958      }
4959    }
4960    return result;
4961  }
4962
4963  private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4964    //  List<Base> result = new ArrayList<Base>();
4965    //  result.add(new BooleanType(convertToBoolean(focus)));
4966    //  return result;
4967    throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toDateTime");
4968  }
4969
4970  private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4971    //  List<Base> result = new ArrayList<Base>();
4972    //  result.add(new BooleanType(convertToBoolean(focus)));
4973    //  return result;
4974    throw makeException(expr, I18nConstants.FHIRPATH_NOT_IMPLEMENTED, "toTime");
4975  }
4976
4977
4978  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
4979    String s = convertToString(focus);
4980    List<Base> result = new ArrayList<Base>();
4981    if (Utilities.isDecimal(s, true)) {
4982      result.add(new DecimalType(s).noExtensions());
4983    }
4984    if ("true".equals(s)) {
4985      result.add(new DecimalType(1).noExtensions());
4986    }
4987    if ("false".equals(s)) {
4988      result.add(new DecimalType(0).noExtensions());
4989    }
4990    return result;
4991  }
4992
4993
4994  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
4995    if (focus.size() > 1) {
4996      throw makeException(exp, I18nConstants.FHIRPATH_NO_COLLECTION, "iif", focus.size());    
4997    }
4998    
4999    List<Base> n1 = execute(focus.isEmpty() ? context : changeThis(context, focus.get(0)), focus, exp.getParameters().get(0), true);
5000    Equality v = asBool(n1, exp);
5001    if (v == Equality.True) {
5002      return execute(context, focus, exp.getParameters().get(1), true);
5003    } else if (exp.getParameters().size() < 3) {
5004      return new ArrayList<Base>();
5005    } else {
5006      return execute(context, focus, exp.getParameters().get(2), true);
5007    }
5008  }
5009
5010
5011  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5012    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
5013    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
5014
5015    List<Base> result = new ArrayList<Base>();
5016    for (int i = 0; i < Math.min(focus.size(), i1); i++) {
5017      result.add(focus.get(i));
5018    }
5019    return result;
5020  }
5021
5022
5023  private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5024    List<Base> result = new ArrayList<Base>();
5025    for (Base item : focus) {
5026      if (!doContains(result, item)) {
5027        result.add(item);
5028      }
5029    }
5030    for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) {
5031      if (!doContains(result, item)) {
5032        result.add(item);
5033      }
5034    }
5035    return result;
5036  }
5037
5038  private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5039    List<Base> result = new ArrayList<Base>();
5040    for (Base item : focus) {
5041      result.add(item);
5042    }
5043    for (Base item : execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true)) {
5044      result.add(item);
5045    }
5046    return result;
5047  }
5048
5049  private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5050    List<Base> result = new ArrayList<Base>();
5051    List<Base> other = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true);
5052
5053    for (Base item : focus) {
5054      if (!doContains(result, item) && doContains(other, item)) {
5055        result.add(item);
5056      }
5057    }
5058    return result;    
5059  }
5060
5061  private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5062    List<Base> result = new ArrayList<Base>();
5063    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
5064
5065    for (Base item : focus) {
5066      if (!doContains(other, item)) {
5067        result.add(item);
5068      }
5069    }
5070    return result;
5071  }
5072
5073
5074  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
5075    if (focus.size() == 1) {
5076      return focus;
5077    }
5078    throw makeException(expr, I18nConstants.FHIRPATH_NO_COLLECTION, "single", focus.size());
5079  }
5080
5081
5082  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws PathEngineException {
5083    if (focus.size() == 0 || focus.size() > 1) {
5084      return makeNull();
5085    }
5086    String ns = null;
5087    String n = null;
5088
5089    ExpressionNode texp = expr.getParameters().get(0);
5090    if (texp.getKind() != Kind.Name) {
5091      throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "0", "is");
5092    }
5093    if (texp.getInner() != null) {
5094      if (texp.getInner().getKind() != Kind.Name) {
5095        throw makeException(expr, I18nConstants.FHIRPATH_PARAM_WRONG, texp.getKind(), "1", "is");
5096      }
5097      ns = texp.getName();
5098      n = texp.getInner().getName();
5099    } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Date", "Time", "SimpleTypeInfo", "ClassInfo")) {
5100      ns = "System";
5101      n = texp.getName();
5102    } else {
5103      ns = "FHIR";
5104      n = texp.getName();        
5105    }
5106    if (ns.equals("System")) {
5107      if (focus.get(0) instanceof Resource) {
5108        return makeBoolean(false);
5109      }
5110      if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions()) {
5111        String t = Utilities.capitalize(focus.get(0).fhirType());
5112        if (n.equals(t)) {
5113          return makeBoolean(true);
5114        }
5115        if ("Date".equals(t) && n.equals("DateTime")) {
5116          return makeBoolean(true);
5117        } else { 
5118          return makeBoolean(false);
5119        }
5120      } else {
5121        return makeBoolean(false);
5122      }
5123    } else if (ns.equals("FHIR")) {
5124      if (n.equals(focus.get(0).fhirType())) {
5125        return makeBoolean(true);
5126      } else {
5127        StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType());
5128        while (sd != null) {
5129          if (n.equals(sd.getType())) {
5130            return makeBoolean(true);
5131          }
5132          sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5133        }
5134        return makeBoolean(false);
5135      }
5136    } else { 
5137      return makeBoolean(false);
5138    }
5139  }
5140
5141
5142  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5143    List<Base> result = new ArrayList<Base>();
5144    String tn;
5145    if (expr.getParameters().get(0).getInner() != null) {
5146      tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName();
5147    } else {
5148      tn = "FHIR."+expr.getParameters().get(0).getName();
5149    }
5150    if (!isKnownType(tn)) {
5151      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE);
5152    }
5153    if (!doNotEnforceAsSingletonRule && focus.size() > 1) {
5154      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); 
5155    }
5156    
5157    for (Base b : focus) {
5158      if (tn.startsWith("System.")) {
5159        if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 
5160          if (b.hasType(tn.substring(7))) {
5161            result.add(b);
5162          }
5163        }
5164
5165      } else if (tn.startsWith("FHIR.")) {
5166        String tnp = tn.substring(5);
5167        if (b.fhirType().equals(tnp)) {
5168          result.add(b);          
5169        } else {
5170          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5171          while (sd != null) {
5172            if (compareTypeNames(tnp, sd.getType())) {
5173              result.add(b);
5174              break;
5175            }
5176            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5177          }
5178        }
5179      }
5180    }
5181    return result;
5182  }
5183  
5184
5185  private List<Base> funcOfType(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
5186    List<Base> result = new ArrayList<Base>();
5187    String tn;
5188    if (expr.getParameters().get(0).getInner() != null) {
5189      tn = expr.getParameters().get(0).getName()+"."+expr.getParameters().get(0).getInner().getName();
5190    } else {
5191      tn = "FHIR."+expr.getParameters().get(0).getName();
5192    }
5193    if (!isKnownType(tn)) {
5194      throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); 
5195    }
5196
5197    
5198    for (Base b : focus) {
5199      if (tn.startsWith("System.")) {
5200        if (b instanceof Element &&((Element) b).isDisallowExtensions()) { 
5201          if (b.hasType(tn.substring(7))) {
5202            result.add(b);
5203          }
5204        }
5205
5206      } else if (tn.startsWith("FHIR.")) {
5207        String tnp = tn.substring(5);
5208        if (b.fhirType().equals(tnp)) {
5209          result.add(b);          
5210        } else {
5211          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5212          while (sd != null) {
5213            if (tnp.equals(sd.getType())) {
5214              result.add(b);
5215              break;
5216            }
5217            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5218          }
5219        }
5220      } else if (tn.startsWith("CDA.")) {
5221        String tnp = Utilities.pathURL(Constants.NS_CDA_ROOT, "StructureDefinition", tn.substring(4));
5222        if (b.fhirType().equals(tnp)) {
5223          result.add(b);          
5224        } else {
5225          StructureDefinition sd = worker.fetchTypeDefinition(b.fhirType());
5226          while (sd != null) {
5227            if (tnp.equals(sd.getType())) {
5228              result.add(b);
5229              break;
5230            }
5231            sd = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE ? null : worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
5232          }
5233        }
5234      }
5235    }
5236    return result;
5237  }
5238
5239  private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5240    List<Base> result = new ArrayList<Base>();
5241    for (Base item : focus) {
5242      result.add(new ClassTypeInfo(item));
5243    }
5244    return result;
5245  }
5246
5247
5248  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5249    List<Base> result = new ArrayList<Base>();
5250    List<Base> current = new ArrayList<Base>();
5251    current.addAll(focus);
5252    List<Base> added = new ArrayList<Base>();
5253    boolean more = true;
5254    while (more) {
5255      added.clear();
5256      List<Base> pc = new ArrayList<Base>();
5257      for (Base item : current) {
5258        pc.clear();
5259        pc.add(item);
5260        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
5261      }
5262      more = false;
5263      current.clear();
5264      for (Base b : added) {
5265        boolean isnew = true;
5266        for (Base t : result) {
5267          if (b.equalsDeep(t)) {
5268            isnew = false;
5269          }
5270        }
5271        if (isnew) {
5272          result.add(b);
5273          current.add(b);
5274          more = true;
5275        }
5276      }
5277    }
5278    return result;
5279  }
5280
5281
5282  private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5283    List<Base> total = new ArrayList<Base>();
5284    if (exp.parameterCount() > 1) {
5285      total = execute(context, focus, exp.getParameters().get(1), false);
5286    }
5287
5288    List<Base> pc = new ArrayList<Base>();
5289    for (Base item : focus) {
5290      ExecutionContext c = changeThis(context, item);
5291      c.total = total;
5292      c.next();
5293      total = execute(c, pc, exp.getParameters().get(0), true);
5294    }
5295    return total;
5296  }
5297
5298
5299
5300  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5301    if (focus.size() < 1) {
5302      return makeBoolean(true);
5303    }
5304    if (focus.size() == 1) {
5305      return makeBoolean(true);
5306    }
5307
5308    boolean distinct = true;
5309    for (int i = 0; i < focus.size(); i++) {
5310      for (int j = i+1; j < focus.size(); j++) {
5311        Boolean eq = doEquals(focus.get(j), focus.get(i));
5312        if (eq == null) {
5313          return new ArrayList<Base>();
5314        } else if (eq == true) {
5315          distinct = false;
5316          break;
5317        }
5318      }
5319    }
5320    return makeBoolean(distinct);
5321  }
5322
5323
5324  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5325    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
5326
5327    boolean valid = true;
5328    for (Base item : target) {
5329      boolean found = false;
5330      for (Base t : focus) {
5331        if (Base.compareDeep(item, t, false)) {
5332          found = true;
5333          break;
5334        }
5335      }
5336      if (!found) {
5337        valid = false;
5338        break;
5339      }
5340    }
5341    List<Base> result = new ArrayList<Base>();
5342    result.add(new BooleanType(valid).noExtensions());
5343    return result;
5344  }
5345
5346
5347  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5348    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
5349
5350    boolean valid = true;
5351    for (Base item : focus) {
5352      boolean found = false;
5353      for (Base t : target) {
5354        if (Base.compareDeep(item, t, false)) {
5355          found = true;
5356          break;
5357        }
5358      }
5359      if (!found) {
5360        valid = false;
5361        break;
5362      }
5363    }
5364    List<Base> result = new ArrayList<Base>();
5365    result.add(new BooleanType(valid).noExtensions());
5366    return result;
5367  }
5368
5369
5370  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5371    List<Base> result = new ArrayList<Base>();
5372    boolean empty = true;
5373    List<Base> pc = new ArrayList<Base>();
5374    for (Base f : focus) {
5375      if (exp.getParameters().size() == 1) {
5376        pc.clear();
5377        pc.add(f);
5378        Equality v = asBool(execute(changeThis(context, f), pc, exp.getParameters().get(0), true), exp);
5379        if (v == Equality.True) {
5380          empty = false;
5381        }
5382      } else if (!f.isEmpty()) {
5383        empty = false;
5384      }
5385    }
5386    result.add(new BooleanType(!empty).noExtensions());
5387    return result;
5388  }
5389
5390
5391  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5392    List<Base> result = new ArrayList<Base>();
5393    Base refContext = null;
5394    for (Base item : focus) {
5395      String s = convertToString(item);
5396      if (item.fhirType().equals("Reference")) {
5397        refContext = item;
5398        Property p = item.getChildByName("reference");
5399        if (p != null && p.hasValues()) {
5400          s = convertToString(p.getValues().get(0));
5401        } else {
5402          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
5403        }
5404      }
5405      if (item.fhirType().equals("canonical")) {
5406        s = item.primitiveValue();
5407        refContext = item;
5408      }
5409      if (s != null) {
5410        Base res = null;
5411        if (s.startsWith("#")) {
5412          String t = s.substring(1);
5413          Property p = context.rootResource.getChildByName("contained");
5414          if (p != null) {
5415            for (Base c : p.getValues()) {
5416              if (t.equals(c.getIdBase())) {
5417                res = c;
5418                break;
5419              }
5420            }
5421          }
5422        } else if (hostServices != null) {
5423          try {
5424            res = hostServices.resolveReference(this, context.appInfo, s, refContext);
5425          } catch (Exception e) {
5426            res = null;
5427          }
5428        }
5429        if (res != null) {
5430          result.add(res);
5431        }
5432      }
5433    }
5434
5435    return result;
5436  }
5437
5438  private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5439    List<Base> result = new ArrayList<Base>();
5440    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5441    String url = nl.get(0).primitiveValue();
5442
5443    for (Base item : focus) {
5444      List<Base> ext = new ArrayList<Base>();
5445      getChildrenByName(item, "extension", ext);
5446      getChildrenByName(item, "modifierExtension", ext);
5447      for (Base ex : ext) {
5448        List<Base> vl = new ArrayList<Base>();
5449        getChildrenByName(ex, "url", vl);
5450        if (convertToString(vl).equals(url)) {
5451          result.add(ex);
5452        }
5453      }
5454    }
5455    return result;
5456  }
5457
5458  private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5459    List<Base> result = new ArrayList<Base>();
5460    if (exp.getParameters().size() == 1) {
5461      boolean all = true;
5462      List<Base> pc = new ArrayList<Base>();
5463      for (Base item : focus) {
5464        pc.clear();
5465        pc.add(item);
5466        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5467        Equality v = asBool(res, exp);
5468        if (v != Equality.False) {
5469          all = false;
5470          break;
5471        }
5472      }
5473      result.add(new BooleanType(all).noExtensions());
5474    } else { 
5475      boolean all = true;
5476      for (Base item : focus) {
5477        if (!canConvertToBoolean(item)) {
5478          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5479        }
5480
5481        Equality v = asBool(item, true);
5482        if (v != Equality.False) {
5483          all = false;
5484          break;
5485        }
5486      }
5487      result.add(new BooleanType(all).noExtensions());
5488    }
5489    return result;
5490  }
5491
5492  private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5493    List<Base> result = new ArrayList<Base>();
5494    if (exp.getParameters().size() == 1) {
5495      boolean any = false;
5496      List<Base> pc = new ArrayList<Base>();
5497      for (Base item : focus) {
5498        pc.clear();
5499        pc.add(item);
5500        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5501        Equality v = asBool(res, exp);
5502        if (v == Equality.False) {
5503          any = true;
5504          break;
5505        }
5506      }
5507      result.add(new BooleanType(any).noExtensions());
5508    } else {
5509      boolean any = false;
5510      for (Base item : focus) {
5511        if (!canConvertToBoolean(item)) {
5512          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5513        }
5514
5515        Equality v = asBool(item, true);
5516        if (v == Equality.False) {
5517          any = true;
5518          break;
5519        }
5520      }
5521      result.add(new BooleanType(any).noExtensions());
5522    }
5523    return result;
5524  }
5525
5526  private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5527    List<Base> result = new ArrayList<Base>();
5528    if (exp.getParameters().size() == 1) {
5529      boolean all = true;
5530      List<Base> pc = new ArrayList<Base>();
5531      for (Base item : focus) {
5532        pc.clear();
5533        pc.add(item);
5534        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5535        Equality v = asBool(res, exp);
5536        if (v != Equality.True) {
5537          all = false;
5538          break;
5539        }
5540      }
5541      result.add(new BooleanType(all).noExtensions());
5542    } else { 
5543      boolean all = true;
5544      for (Base item : focus) {
5545        if (!canConvertToBoolean(item)) {
5546          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5547        }
5548        Equality v = asBool(item, true);
5549        if (v != Equality.True) {
5550          all = false;
5551          break;
5552        }
5553      }
5554      result.add(new BooleanType(all).noExtensions());
5555    }
5556    return result;
5557  }
5558
5559  private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5560    List<Base> result = new ArrayList<Base>();
5561    if (exp.getParameters().size() == 1) {
5562      boolean any = false;
5563      List<Base> pc = new ArrayList<Base>();
5564      for (Base item : focus) {
5565        pc.clear();
5566        pc.add(item);
5567        List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
5568        Equality v = asBool(res, exp);
5569        if (v == Equality.True) {
5570          any = true;
5571          break;
5572        }
5573      }
5574      result.add(new BooleanType(any).noExtensions());
5575    } else {
5576      boolean any = false;
5577      for (Base item : focus) {
5578        if (!canConvertToBoolean(item)) {
5579          throw new FHIRException("Unable to convert '"+convertToString(item)+"' to a boolean");
5580        }
5581
5582        Equality v = asBool(item, true);
5583        if (v == Equality.True) {
5584          any = true;
5585          break;
5586        }
5587      }
5588      result.add(new BooleanType(any).noExtensions());
5589    }
5590    return result;
5591  }
5592
5593  private boolean canConvertToBoolean(Base item) {
5594    return (item.isBooleanPrimitive());
5595  }
5596
5597  private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5598    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5599    String name = nl.get(0).primitiveValue();
5600    if (exp.getParameters().size() == 2) {
5601      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
5602      log(name, n2);
5603    } else { 
5604      log(name, focus);
5605    }
5606    return focus;
5607  }
5608
5609  private List<Base> funcDefineVariable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5610    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
5611    String name = nl.get(0).primitiveValue();
5612    List<Base> value;
5613    if (exp.getParameters().size() == 2) {
5614      value = execute(context, focus, exp.getParameters().get(1), true);
5615    } else { 
5616      value = focus;
5617    }
5618    // stash the variable into the context
5619    context.setDefinedVariable(name, value);
5620    return focus;
5621  }
5622
5623  private List<Base> funcCheck(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
5624    List<Base> n1 = execute(context, focus, expr.getParameters().get(0), true);
5625    if (!convertToBoolean(n1)) {
5626      List<Base> n2 = execute(context, focus, expr.getParameters().get(1), true);
5627      String name = n2.get(0).primitiveValue();
5628      throw makeException(expr, I18nConstants.FHIRPATH_CHECK_FAILED, name);
5629    }
5630    return focus;
5631  }
5632
5633  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5634    if (focus.size() <= 1) {
5635      return focus;
5636    }
5637
5638    List<Base> result = new ArrayList<Base>();
5639    for (int i = 0; i < focus.size(); i++) {
5640      boolean found = false;
5641      for (int j = i+1; j < focus.size(); j++) {
5642        Boolean eq = doEquals(focus.get(j), focus.get(i));
5643        if (eq == null)
5644          return new ArrayList<Base>();
5645        else if (eq == true) {
5646          found = true;
5647          break;
5648        }
5649      }
5650      if (!found) {
5651        result.add(focus.get(i));
5652      }
5653    }
5654    return result;
5655  }
5656
5657  private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5658    List<Base> result = new ArrayList<Base>();
5659    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5660    String sw = convertToString(swb);
5661
5662    if (focus.size() == 0 || swb.size() == 0) {
5663      //
5664    } else if (focus.size() == 1 && !Utilities.noString(sw)) {
5665      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5666        String st = convertToString(focus.get(0));
5667        if (Utilities.noString(st)) {
5668          result.add(new BooleanType(false).noExtensions());
5669        } else {
5670          Pattern p = Pattern.compile("(?s)" + sw);
5671          Matcher m = p.matcher(st);
5672          boolean ok = m.find();
5673          result.add(new BooleanType(ok).noExtensions());
5674        }
5675      }
5676    } else {
5677      result.add(new BooleanType(false).noExtensions());
5678    }
5679    return result;
5680  }
5681
5682  private List<Base> funcMatchesFull(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5683    List<Base> result = new ArrayList<Base>();
5684    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
5685
5686    if (focus.size() == 1 && !Utilities.noString(sw)) {
5687      if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5688        String st = convertToString(focus.get(0));
5689        if (Utilities.noString(st)) {
5690          result.add(new BooleanType(false).noExtensions());
5691        } else {
5692          Pattern p = Pattern.compile("(?s)" + sw);
5693          Matcher m = p.matcher(st);
5694          boolean ok = m.matches();
5695          result.add(new BooleanType(ok).noExtensions());
5696        }
5697      }
5698    } else {
5699      result.add(new BooleanType(false).noExtensions());
5700    }
5701    return result;
5702  }
5703
5704  private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5705    List<Base> result = new ArrayList<Base>();
5706    List<Base> swb = execute(context, baseToList(context.thisItem), exp.getParameters().get(0), true);
5707    String sw = convertToString(swb);
5708
5709    if (focus.size() != 1) {
5710      //
5711    } else if (swb.size() != 1) {
5712        //
5713    } else if (Utilities.noString(sw)) {
5714      result.add(new BooleanType(true).noExtensions());
5715    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5716      String st = convertToString(focus.get(0));
5717      if (Utilities.noString(st)) {
5718        result.add(new BooleanType(false).noExtensions());
5719      } else {
5720        result.add(new BooleanType(st.contains(sw)).noExtensions());
5721      }
5722    } 
5723    return result;
5724  }
5725
5726  private List<Base> baseToList(Base b) {
5727    List<Base> res = new ArrayList<>();
5728    res.add(b);
5729    return res;
5730  }
5731
5732  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5733    List<Base> result = new ArrayList<Base>();
5734    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5735      String s = convertToString(focus.get(0));
5736      result.add(new IntegerType(s.length()).noExtensions());
5737    }
5738    return result;
5739  }
5740
5741  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5742    List<Base> result = new ArrayList<Base>();
5743    if (focus.size() == 1) {
5744      String s = convertToString(focus.get(0));
5745      result.add(new BooleanType(!Utilities.noString(s)).noExtensions());
5746    } else {
5747      result.add(new BooleanType(false).noExtensions());
5748    }
5749    return result;
5750  }
5751
5752  private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5753    List<Base> result = new ArrayList<Base>();
5754    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5755    String sw = convertToString(swb);
5756
5757    if (focus.size() == 0) {
5758      // no result
5759    } else if (swb.size() == 0) {
5760      // no result
5761    } else if (Utilities.noString(sw)) {
5762      result.add(new BooleanType(true).noExtensions());
5763    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5764      String s = convertToString(focus.get(0));
5765      if (s == null) {
5766        result.add(new BooleanType(false).noExtensions());
5767      } else {
5768        result.add(new BooleanType(s.startsWith(sw)).noExtensions());
5769      }
5770    }
5771    return result;
5772  }
5773
5774  private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5775    List<Base> result = new ArrayList<Base>();
5776    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5777      String s = convertToString(focus.get(0));
5778      if (!Utilities.noString(s)) { 
5779        result.add(new StringType(s.toLowerCase()).noExtensions());
5780      }
5781    }
5782    return result;
5783  }
5784
5785  private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5786    List<Base> result = new ArrayList<Base>();
5787    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5788      String s = convertToString(focus.get(0));
5789      if (!Utilities.noString(s)) { 
5790        result.add(new StringType(s.toUpperCase()).noExtensions());
5791      }
5792    }
5793    return result;
5794  }
5795
5796  private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5797    List<Base> result = new ArrayList<Base>();
5798    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5799      String s = convertToString(focus.get(0));
5800      for (char c : s.toCharArray()) {  
5801        result.add(new StringType(String.valueOf(c)).noExtensions());
5802      }
5803    }
5804    return result;
5805  }
5806
5807  private List<Base> funcIndexOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5808    List<Base> result = new ArrayList<Base>();
5809
5810    List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
5811    String sw = convertToString(swb);
5812    if (focus.size() == 0) {
5813      // no result
5814    } else if (swb.size() == 0) {
5815      // no result
5816    } else if (Utilities.noString(sw)) {
5817      result.add(new IntegerType(0).noExtensions());
5818    } else if (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion) {
5819      String s = convertToString(focus.get(0));
5820      if (s == null) {
5821        result.add(new IntegerType(0).noExtensions());
5822      } else {
5823        result.add(new IntegerType(s.indexOf(sw)).noExtensions());
5824      }
5825    }
5826    return result;
5827  }
5828
5829  private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
5830    List<Base> result = new ArrayList<Base>();
5831    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
5832    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
5833    int i2 = -1;
5834    if (exp.parameterCount() == 2) {
5835      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
5836      if (n2.isEmpty()|| !n2.get(0).isPrimitive() || !Utilities.isInteger(n2.get(0).primitiveValue())) {
5837        return new ArrayList<Base>();
5838      }
5839      i2 = Integer.parseInt(n2.get(0).primitiveValue());
5840    }
5841
5842    if (focus.size() == 1 && (focus.get(0).hasType(FHIR_TYPES_STRING) || doImplicitStringConversion)) {
5843      String sw = convertToString(focus.get(0));
5844      String s;
5845      if (i1 < 0 || i1 >= sw.length()) {
5846        return new ArrayList<Base>();
5847      }
5848      if (exp.parameterCount() == 2) {
5849        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
5850      } else {
5851        s = sw.substring(i1);
5852      }
5853      if (!Utilities.noString(s)) { 
5854        result.add(new StringType(s).noExtensions());
5855      }
5856    }
5857    return result;
5858  }
5859
5860  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5861    String s = convertToString(focus);
5862    List<Base> result = new ArrayList<Base>();
5863    if (Utilities.isInteger(s)) {
5864      result.add(new IntegerType(s).noExtensions());
5865    } else if ("true".equals(s)) {
5866      result.add(new IntegerType(1).noExtensions());
5867    } else if ("false".equals(s)) {
5868      result.add(new IntegerType(0).noExtensions());
5869    }
5870    return result;
5871  }
5872
5873  private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5874    List<Base> result = new ArrayList<Base>();
5875    if (focus.size() != 1) {
5876      result.add(new BooleanType(false).noExtensions());
5877    } else if (focus.get(0) instanceof IntegerType) {
5878      result.add(new BooleanType(true).noExtensions());
5879    } else if (focus.get(0) instanceof BooleanType) {
5880      result.add(new BooleanType(true).noExtensions());
5881    } else if (focus.get(0) instanceof StringType) {
5882      result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions());
5883    } else { 
5884      result.add(new BooleanType(false).noExtensions());
5885    }
5886    return result;
5887  }
5888
5889  private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5890    List<Base> result = new ArrayList<Base>();
5891    if (focus.size() != 1) {
5892      result.add(new BooleanType(false).noExtensions());
5893    } else if (focus.get(0) instanceof IntegerType) {
5894      result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions());
5895    } else if (focus.get(0) instanceof DecimalType) {
5896      result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions());
5897    } else if (focus.get(0) instanceof BooleanType) {
5898      result.add(new BooleanType(true).noExtensions());
5899    } else if (focus.get(0) instanceof StringType) {
5900      result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions());
5901    } else { 
5902      result.add(new BooleanType(false).noExtensions());
5903    }
5904    return result;
5905  }
5906
5907  private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5908    List<Base> result = new ArrayList<Base>();
5909    if (focus.size() != 1) {
5910      result.add(new BooleanType(false).noExtensions());
5911    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
5912      result.add(new BooleanType(true).noExtensions());
5913    } else if (focus.get(0) instanceof StringType) {
5914      result.add(new BooleanType((convertToString(focus.get(0)).matches
5915          ("([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());
5916    } else { 
5917      result.add(new BooleanType(false).noExtensions());
5918    }
5919    return result;
5920  }
5921
5922  private List<Base> funcIsDate(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5923    List<Base> result = new ArrayList<Base>();
5924    if (focus.size() != 1) {
5925      result.add(new BooleanType(false).noExtensions());
5926    } else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType) {
5927      result.add(new BooleanType(true).noExtensions());
5928    } else if (focus.get(0) instanceof StringType) {
5929      result.add(new BooleanType((convertToString(focus.get(0)).matches
5930          ("([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());
5931    } else { 
5932      result.add(new BooleanType(false).noExtensions());
5933    }
5934    return result;
5935  }
5936
5937  private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode expr) throws FHIRException {
5938    if (hostServices == null) {
5939      throw makeException(expr, I18nConstants.FHIRPATH_HO_HOST_SERVICES, "conformsTo");
5940    }
5941    List<Base> result = new ArrayList<Base>();
5942    if (focus.size() != 1) {
5943      result.add(new BooleanType(false).noExtensions());
5944    } else {
5945      String url = convertToString(execute(context, focus, expr.getParameters().get(0), true));
5946      result.add(new BooleanType(hostServices.conformsToProfile(this, context.appInfo,  focus.get(0), url)).noExtensions());
5947    }
5948    return result;
5949  }
5950
5951  private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5952    List<Base> result = new ArrayList<Base>();
5953    if (focus.size() != 1) {
5954      result.add(new BooleanType(false).noExtensions());
5955    } else if (focus.get(0) instanceof TimeType) {
5956      result.add(new BooleanType(true).noExtensions());
5957    } else if (focus.get(0) instanceof StringType) {
5958      result.add(new BooleanType((convertToString(focus.get(0)).matches
5959          ("(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());
5960    } else {
5961      result.add(new BooleanType(false).noExtensions());
5962    }
5963    return result;
5964  }
5965
5966  private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5967    List<Base> result = new ArrayList<Base>();
5968    if (focus.size() != 1) {
5969      result.add(new BooleanType(false).noExtensions());
5970    } else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType)) {
5971      result.add(new BooleanType(true).noExtensions());
5972    } else { 
5973      result.add(new BooleanType(false).noExtensions());
5974    }
5975    return result;
5976  }
5977
5978  private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
5979    List<Base> result = new ArrayList<Base>();
5980    if (focus.size() != 1) {
5981      result.add(new BooleanType(false).noExtensions());
5982    } else if (focus.get(0) instanceof IntegerType) {
5983      result.add(new BooleanType(true).noExtensions());
5984    } else if (focus.get(0) instanceof DecimalType) {
5985      result.add(new BooleanType(true).noExtensions());
5986    } else if (focus.get(0) instanceof Quantity) {
5987      result.add(new BooleanType(true).noExtensions());
5988    } else if (focus.get(0) instanceof BooleanType) {
5989      result.add(new BooleanType(true).noExtensions());
5990    } else  if (focus.get(0) instanceof StringType) {
5991      Quantity q = parseQuantityString(focus.get(0).primitiveValue());
5992      result.add(new BooleanType(q != null).noExtensions());
5993    } else {
5994      result.add(new BooleanType(false).noExtensions());
5995    }
5996    return result;
5997  }
5998
5999  public Quantity parseQuantityString(String s) {
6000    if (s == null) {
6001      return null;
6002    }
6003    s = s.trim();
6004    if (s.contains(" ")) {
6005      String v = s.substring(0, s.indexOf(" ")).trim();
6006      s = s.substring(s.indexOf(" ")).trim();
6007      if (!Utilities.isDecimal(v, false)) {
6008        return null;
6009      }
6010      if (s.startsWith("'") && s.endsWith("'")) {
6011        return Quantity.fromUcum(v, s.substring(1, s.length()-1));
6012      }
6013      if (s.equals("year") || s.equals("years")) {
6014        return Quantity.fromUcum(v, "a");
6015      } else if (s.equals("month") || s.equals("months")) {
6016        return Quantity.fromUcum(v, "mo_s");
6017      } else if (s.equals("week") || s.equals("weeks")) {
6018        return Quantity.fromUcum(v, "wk");
6019      } else if (s.equals("day") || s.equals("days")) {
6020        return Quantity.fromUcum(v, "d");
6021      } else if (s.equals("hour") || s.equals("hours")) {
6022        return Quantity.fromUcum(v, "h");
6023      } else if (s.equals("minute") || s.equals("minutes")) {
6024        return Quantity.fromUcum(v, "min");
6025      } else if (s.equals("second") || s.equals("seconds")) {
6026        return Quantity.fromUcum(v, "s");
6027      } else if (s.equals("millisecond") || s.equals("milliseconds")) {
6028        return Quantity.fromUcum(v, "ms");
6029      } else {
6030        return null;
6031      } 
6032    } else {
6033      if (Utilities.isDecimal(s, true)) {
6034        return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1");
6035      } else {
6036        return null;
6037      } 
6038    }
6039  }
6040
6041
6042  private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6043    List<Base> result = new ArrayList<Base>();
6044    if (focus.size() != 1) {
6045      result.add(new BooleanType(false).noExtensions());
6046    } else if (focus.get(0) instanceof IntegerType) {
6047      result.add(new BooleanType(true).noExtensions());
6048    } else if (focus.get(0) instanceof BooleanType) {
6049      result.add(new BooleanType(true).noExtensions());
6050    } else if (focus.get(0) instanceof DecimalType) {
6051      result.add(new BooleanType(true).noExtensions());
6052    } else if (focus.get(0) instanceof StringType) {
6053      result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)), true)).noExtensions());
6054    } else {
6055      result.add(new BooleanType(false).noExtensions());
6056    } 
6057    return result;
6058  }
6059
6060  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6061    List<Base> result = new ArrayList<Base>();
6062    result.add(new IntegerType(focus.size()).noExtensions());
6063    return result;
6064  }
6065
6066  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6067    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
6068    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
6069
6070    List<Base> result = new ArrayList<Base>();
6071    for (int i = i1; i < focus.size(); i++) {
6072      result.add(focus.get(i));
6073    } 
6074    return result;
6075  }
6076
6077  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6078    List<Base> result = new ArrayList<Base>();
6079    for (int i = 1; i < focus.size(); i++) {
6080      result.add(focus.get(i));
6081    } 
6082    return result;
6083  }
6084
6085  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6086    List<Base> result = new ArrayList<Base>();
6087    if (focus.size() > 0) {
6088      result.add(focus.get(focus.size()-1));
6089    } 
6090    return result;
6091  }
6092
6093  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6094    List<Base> result = new ArrayList<Base>();
6095    if (focus.size() > 0) {
6096      result.add(focus.get(0));
6097    } 
6098    return result;
6099  }
6100
6101
6102  private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6103    List<Base> result = new ArrayList<Base>();
6104    List<Base> pc = new ArrayList<Base>();
6105    for (Base item : focus) {
6106      pc.clear();
6107      pc.add(item);
6108      Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true), exp);
6109      if (v == Equality.True) {
6110        result.add(item);
6111      } 
6112    }
6113    return result;
6114  }
6115
6116  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6117    List<Base> result = new ArrayList<Base>();
6118    List<Base> pc = new ArrayList<Base>();
6119    int i = 0;
6120    for (Base item : focus) {
6121      pc.clear();
6122      pc.add(item);
6123      result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true));
6124      i++;
6125    }
6126    return result;
6127  }
6128
6129
6130  private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
6131    List<Base> result = new ArrayList<Base>();
6132    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
6133    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size()) {
6134      result.add(focus.get(Integer.parseInt(s)));
6135    } 
6136    return result;
6137  }
6138
6139  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
6140    List<Base> result = new ArrayList<Base>();
6141    result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions());
6142    return result;
6143  }
6144
6145  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
6146    List<Base> result = new ArrayList<Base>();  
6147    Equality v = asBool(focus, exp);
6148    if (v != Equality.Null) {
6149      result.add(new BooleanType(v != Equality.True));
6150    } 
6151    return result;
6152  }
6153
6154  private class ElementDefinitionMatch {
6155    private ElementDefinition definition;
6156    private ElementDefinition sourceDefinition; // if there was a content reference
6157    private String fixedType;
6158    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
6159      super();
6160      this.definition = definition;
6161      this.fixedType = fixedType;
6162    }
6163    public ElementDefinition getDefinition() {
6164      return definition;
6165    }
6166    public ElementDefinition getSourceDefinition() {
6167      return sourceDefinition;
6168    }
6169    public String getFixedType() {
6170      return fixedType;
6171    }
6172
6173  }
6174
6175  private void getChildTypesByName(String type, String name, TypeDetails result, ExpressionNode expr, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
6176    if (Utilities.noString(type)) {
6177      throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, "", "getChildTypesByName");
6178    } 
6179    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) {
6180      return;
6181    }     
6182
6183    if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 
6184      getSimpleTypeChildTypesByName(name, result);
6185    } else if (type.equals(TypeDetails.FP_ClassInfo)) { 
6186      getClassInfoChildTypesByName(name, result);
6187    } else {
6188      if (type.startsWith(Constants.NS_SYSTEM_TYPE)) {
6189        return;
6190      } 
6191      
6192      String url = null;
6193      if (type.contains("#")) {
6194        url = type.substring(0, type.indexOf("#"));
6195      } else {
6196        url = type;
6197      }
6198      String tail = "";
6199      StructureDefinition sd = worker.fetchTypeDefinition(url);
6200      if (sd == null) {
6201        sd = worker.fetchResource(StructureDefinition.class, url);
6202      }
6203      if (sd == null) {
6204        if (url.startsWith(TypeDetails.FP_NS)) {
6205          return;
6206        } else {
6207          throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName#1");          
6208        }
6209      }
6210      List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
6211      ElementDefinitionMatch m = null;
6212      if (type.contains("#")) {
6213        List<ElementDefinitionMatch> list = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false, expr);
6214        m = list.size() == 1 ? list.get(0) : null;
6215      }
6216      if (m != null && hasDataType(m.definition)) {
6217        if (m.fixedType != null)  {
6218          StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, null), sd);
6219          if (dt == null) {
6220            throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(m.fixedType, null), "getChildTypesByName#2");
6221          }
6222          sdl.add(dt);
6223        } else
6224          for (TypeRefComponent t : m.definition.getType()) {
6225            StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), null));
6226            if (dt == null) {
6227              throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, ProfileUtilities.sdNs(t.getCode(), null), "getChildTypesByName#3");
6228            }
6229            addTypeAndDescendents(sdl, dt, cu.allStructures());
6230            // also add any descendant types
6231          }
6232      } else {
6233        addTypeAndDescendents(sdl, sd, cu.allStructures());
6234        if (type.contains("#")) {
6235          tail = type.substring(type.indexOf("#")+1);
6236          if (tail.contains(".")) {
6237            tail = tail.substring(tail.indexOf("."));
6238          } else {
6239            tail = "";
6240          }
6241        }
6242      }
6243
6244      for (StructureDefinition sdi : sdl) {
6245        String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
6246        if (name.equals("**")) {
6247          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
6248          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
6249            if (ed.getPath().startsWith(path)) {
6250              if (ed.hasContentReference()) {
6251                String cpath = ed.getContentReference();
6252                String tn = sdi.getType()+cpath;
6253                if (!result.hasType(worker, tn)) {
6254                  if (elementDependencies != null) {
6255                    elementDependencies.add(ed);
6256                  }
6257                  getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies);
6258                }
6259              } else {
6260                for (TypeRefComponent t : ed.getType()) {
6261                  if (t.hasCode() && t.getCodeElement().hasValue()) {
6262                    String tn = null;
6263                    if (Utilities.existsInList(t.getCode(), "Element", "BackboneElement", "Base") || cu.isAbstractType(t.getCode())) {
6264                      tn = sdi.getType()+"#"+ed.getPath();
6265                    } else {
6266                      tn = t.getCode();
6267                    }
6268                    if (t.getCode().equals("Resource")) {
6269                      for (String rn : worker.getResourceNames()) {
6270                        if (!result.hasType(worker, rn)) {
6271                          if (elementDependencies != null) {
6272                            elementDependencies.add(ed);
6273                          }
6274                          getChildTypesByName(result.addType(rn), "**", result, expr, null, elementDependencies);
6275                        }                  
6276                      }
6277                    } else if (!result.hasType(worker, tn)) {
6278                      if (elementDependencies != null) {
6279                        elementDependencies.add(ed);
6280                      }
6281                      getChildTypesByName(result.addType(tn), "**", result, expr, null, elementDependencies);
6282                    }
6283                  }
6284                }
6285              }
6286            }
6287          }      
6288        } else if (name.equals("*")) {
6289          assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
6290          for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
6291            if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
6292              for (TypeRefComponent t : ed.getType()) {
6293                if (Utilities.noString(t.getCode())) { // Element.id or Extension.url
6294                  if (elementDependencies != null) {
6295                    elementDependencies.add(ed);
6296                  }
6297                  result.addType("System.string");
6298                } else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement")) {
6299                  if (elementDependencies != null) {
6300                    elementDependencies.add(ed);
6301                  }
6302                  result.addType(sdi.getType()+"#"+ed.getPath());
6303                } else if (t.getCode().equals("Resource")) {
6304                  if (elementDependencies != null) {
6305                    elementDependencies.add(ed);
6306                  }
6307                  result.addTypes(worker.getResourceNames());
6308                } else {
6309                  if (elementDependencies != null) {
6310                    elementDependencies.add(ed);
6311                  }
6312                  result.addType(t.getCode());
6313                  copyTargetProfiles(ed, t, focus, result);
6314                }
6315              }
6316          }
6317        } else {
6318          path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
6319
6320          List<ElementDefinitionMatch> edl = getElementDefinition(sdi, path, isAllowPolymorphicNames(), expr);
6321          for (ElementDefinitionMatch ed : edl) {
6322            if (ed.getDefinition().isChoice()) {
6323              result.setChoice(true);
6324            }
6325            if (!Utilities.noString(ed.getFixedType())) {
6326              if (elementDependencies != null) {
6327                elementDependencies.add(ed.definition);
6328              }
6329              result.addType(ed.getFixedType());
6330            } else if (ed.getSourceDefinition() != null) {
6331              ProfiledType pt = new ProfiledType(sdi.getType()+"#"+ed.definition.getPath());
6332              result.addType(ed.getSourceDefinition().unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt);
6333            } else {
6334              for (TypeRefComponent t : ed.getDefinition().getType()) {
6335                if (Utilities.noString(t.getCode())) {
6336                  if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) { 
6337                    if (elementDependencies != null) {
6338                      elementDependencies.add(ed.definition);
6339                    }
6340                    result.addType(TypeDetails.FP_NS, "System.String");
6341                  }
6342                  break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
6343                }
6344
6345                ProfiledType pt = null;
6346                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement") || isAbstractType(t.getCode())) {
6347                  pt = new ProfiledType(sdi.getUrl()+"#"+path);
6348                } else if (t.getCode().equals("Resource")) {
6349                  if (elementDependencies != null) {
6350                    elementDependencies.add(ed.definition);
6351                  }
6352                  result.addTypes(worker.getResourceNames());
6353                } else {
6354                  pt = new ProfiledType(t.getCode());
6355                }
6356                if (pt != null) {
6357                  if (t.hasProfile()) {
6358                    pt.addProfiles(t.getProfile());
6359                  }
6360                  if (ed.getDefinition().hasBinding()) {
6361                    pt.addBinding(ed.getDefinition().getBinding());
6362                  }
6363                  if (elementDependencies != null) {
6364                    elementDependencies.add(ed.definition);
6365                  }
6366                  result.addType(ed.definition.unbounded() ? CollectionStatus.ORDERED : CollectionStatus.SINGLETON, pt);
6367                  copyTargetProfiles(ed.getDefinition(), t, focus, result);
6368                }
6369              }
6370            }
6371          }
6372        }
6373      }
6374    }
6375  }
6376
6377  private void copyTargetProfiles(ElementDefinition ed, TypeRefComponent t, TypeDetails focus, TypeDetails result) {
6378    if (t.hasTargetProfile()) {
6379      for (CanonicalType u : t.getTargetProfile()) {
6380        result.addTarget(u.primitiveValue());
6381      }
6382    } else if (focus != null && focus.hasType("CodeableReference") && ed.getPath().endsWith(".reference") && focus.getTargets() != null) { // special case, targets are on parent
6383      for (String s : focus.getTargets()) {
6384        result.addTarget(s);
6385      }
6386    }
6387  }
6388
6389  private void addTypeAndDescendents(List<StructureDefinition> sdl, StructureDefinition dt, List<StructureDefinition> types) {
6390    sdl.add(dt);
6391    for (StructureDefinition sd : types) {
6392      if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(dt.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
6393        addTypeAndDescendents(sdl, sd, types);
6394      }
6395    }  
6396  }
6397
6398  private void getClassInfoChildTypesByName(String name, TypeDetails result) {
6399    if (name.equals("namespace")) {
6400      result.addType(TypeDetails.FP_String);
6401    }
6402    if (name.equals("name")) {
6403      result.addType(TypeDetails.FP_String);
6404    }
6405  }
6406
6407
6408  private void getSimpleTypeChildTypesByName(String name, TypeDetails result) {
6409    if (name.equals("namespace")) {
6410      result.addType(TypeDetails.FP_String);
6411    }
6412    if (name.equals("name")) {
6413      result.addType(TypeDetails.FP_String);
6414    }
6415  }
6416
6417
6418  public List<ElementDefinitionMatch> getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
6419    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
6420      if (ed.getPath().equals(path)) {
6421        if (ed.hasContentReference()) {
6422          ElementDefinitionMatch res = getElementDefinitionById(sd, ed.getContentReference());
6423          if (res == null) {
6424            throw new Error("Unable to find "+ed.getContentReference());
6425          } else {
6426            res.sourceDefinition = ed;
6427          }
6428          return ml(res);
6429        } else {
6430          return ml(new ElementDefinitionMatch(ed, null));
6431        }
6432      }
6433      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3) {
6434        return ml(new ElementDefinitionMatch(ed, null));
6435      }
6436      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
6437        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
6438        if (primitiveTypes.contains(s)) {
6439          return ml(new ElementDefinitionMatch(ed, s));
6440        } else {
6441          return ml(new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3)));
6442        }
6443      }
6444      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 
6445        // now we walk into the type.
6446        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
6447          List<ElementDefinitionMatch> list = new ArrayList<>();
6448          // for each type, does it have the next node in the path? 
6449          for (TypeRefComponent tr : ed.getType()) {
6450            StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tr.getCode(), null), sd);
6451            if (nsd == null) { 
6452              throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition");
6453            }
6454            List<ElementDefinitionMatch> edl = getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr);
6455            list.addAll(edl);
6456          }
6457          return list;
6458        }
6459        StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), null), sd);
6460        if (nsd == null) { 
6461          throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, ed.getType().get(0).getCode(), "getElementDefinition");
6462        }
6463        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName, expr);
6464      }
6465      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
6466        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
6467        List<ElementDefinitionMatch> res = getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName, expr);
6468        if (res.size() == 0) {
6469          throw new Error("Unable to find "+ed.getContentReference());
6470        } else {
6471          for (ElementDefinitionMatch item : res) {
6472            item.sourceDefinition = ed;
6473          }
6474        }
6475        return res;
6476      }
6477    }
6478    return ml(null);
6479  }
6480
6481  private List<ElementDefinitionMatch> ml(ElementDefinitionMatch item) {
6482    List<ElementDefinitionMatch> list = new ArrayList<>();
6483    if (item != null) {
6484      list.add(item);
6485    }
6486    return list;
6487  }
6488
6489  private boolean isAbstractType(List<TypeRefComponent> list) {
6490    if (list.size() != 1) {
6491      return false;
6492    } else {
6493      return isAbstractType(list.get(0).getCode());
6494    }
6495  }
6496
6497  private boolean isAbstractType(String code) {
6498    StructureDefinition sd = worker.fetchTypeDefinition(code);
6499    return sd != null && sd.getAbstract() && sd.getKind() != StructureDefinitionKind.RESOURCE;
6500  }
6501
6502  
6503  private boolean hasType(ElementDefinition ed, String s) {
6504    for (TypeRefComponent t : ed.getType()) {
6505      if (s.equalsIgnoreCase(t.getCode())) {
6506        return true;
6507      }
6508    }
6509    return false;
6510  }
6511
6512  private boolean hasDataType(ElementDefinition ed) {
6513    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement") || isAbstractType(ed.getType().get(0).getCode()));
6514  }
6515
6516  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
6517    if (ref.startsWith(sd.getUrl()+"#")) {
6518      ref = ref.replace(sd.getUrl()+"#", "#"); 
6519    }
6520    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
6521      if (ref.equals("#"+ed.getId())) {
6522        return new ElementDefinitionMatch(ed, null);
6523      }
6524    }
6525    return null;
6526  }
6527
6528
6529  public boolean hasLog() {
6530    return log != null && log.length() > 0;
6531  }
6532
6533
6534  public String takeLog() {
6535    if (!hasLog()) {
6536      return "";
6537    }
6538    String s = log.toString();
6539    log = new StringBuilder();
6540    return s;
6541  }
6542
6543
6544  /** given an element definition in a profile, what element contains the differentiating fixed 
6545   * for the element, given the differentiating expresssion. The expression is only allowed to 
6546   * use a subset of FHIRPath
6547   * 
6548   * @param profile
6549   * @param element
6550   * @return
6551   * @throws PathEngineException 
6552   * @throws DefinitionException 
6553   */
6554  public TypedElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, TypedElementDefinition element, StructureDefinition source, boolean dontWalkIntoReferences) throws DefinitionException {
6555    StructureDefinition sd = profile;
6556    TypedElementDefinition focus = null;
6557    boolean okToNotResolve = false;
6558
6559    if (expr.getKind() == Kind.Name) {
6560      if (element.getElement().hasSlicing()) {
6561        ElementDefinition slice = pickMandatorySlice(sd, element.getElement());
6562        if (slice == null) {
6563          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED, element.getElement().getId());
6564        }
6565        element = new TypedElementDefinition(slice);
6566      }
6567
6568      if (expr.getName().equals("$this")) {
6569        focus = element;
6570      } else { 
6571        SourcedChildDefinitions childDefinitions;
6572        childDefinitions = profileUtilities.getChildMap(sd, element.getElement());
6573        // if that's empty, get the children of the type
6574        if (childDefinitions.getList().isEmpty()) {
6575
6576          sd = fetchStructureByType(element, expr);
6577          if (sd == null) {
6578            throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId());
6579          }
6580          childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
6581        }
6582        for (ElementDefinition t : childDefinitions.getList()) {
6583          if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) 
6584            focus = new TypedElementDefinition(t);
6585            break;
6586          }
6587        }
6588      }
6589    } else if (expr.getKind() == Kind.Function) {
6590      if ("resolve".equals(expr.getName())) {
6591        if (element.getTypes().size() == 0) {
6592          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NO_TYPE, element.getElement().getId());
6593        }
6594        if (element.getTypes().size() > 1) {
6595          throw makeExceptionPlural(element.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_MULTIPLE_TYPES, element.getElement().getId());
6596        }
6597        if (!element.getTypes().get(0).hasTarget()) {
6598          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_RESOLVE_NOT_REFERENCE, element.getElement().getId(), element.getElement().getType().get(0).getCode()+")");
6599        }
6600        if (element.getTypes().get(0).getTargetProfile().size() > 1) {
6601          throw makeExceptionPlural(element.getTypes().get(0).getTargetProfile().size(), expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_NO_TARGET, element.getElement().getId());
6602        }
6603        sd = worker.fetchResource(StructureDefinition.class, element.getTypes().get(0).getTargetProfile().get(0).getValue(), profile);
6604        if (sd == null) {
6605          throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getTypes().get(0).getTargetProfile(), element.getElement().getId());
6606        }
6607        focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep());
6608      } else if ("extension".equals(expr.getName())) {
6609        String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue();
6610        SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement());
6611        for (ElementDefinition t : childDefinitions.getList()) {
6612          if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
6613            StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ?
6614                null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue(), profile);
6615            while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) {
6616              exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd);
6617            }
6618            if (exsd != null && exsd.getUrl().equals(targetUrl)) {
6619              if (profileUtilities.getChildMap(sd, t).getList().isEmpty()) {
6620                sd = exsd;
6621              }
6622              focus = new TypedElementDefinition(t);
6623              break;
6624            }
6625          }
6626        }
6627        if (focus == null) { 
6628          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getElement().getId(), sd.getUrl());
6629        }
6630      } else if ("ofType".equals(expr.getName())) {
6631        if (!element.getElement().hasType()) {
6632          throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getElement().getId());
6633        }
6634        List<String> atn = new ArrayList<>();
6635        for (TypeRefComponent tr : element.getTypes()) {
6636          if (!tr.hasCode()) {
6637            throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NO_CODE, element.getElement().getId());
6638          }
6639          atn.add(tr.getCode());
6640        }
6641        String stn = expr.getParameters().get(0).getName();  
6642        okToNotResolve = true;
6643        if ((atn.contains(stn))) {
6644          if (element.getTypes().size() > 1) {
6645            focus = new TypedElementDefinition( element.getSrc(), element.getElement(), stn);
6646          } else {
6647            focus = element;
6648          }
6649        }
6650      } else {
6651        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName());
6652      }
6653    } else if (expr.getKind() == Kind.Group) {
6654      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString());
6655    } else if (expr.getKind() == Kind.Constant) {
6656      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST);
6657    }
6658
6659    if (focus == null) { 
6660      if (okToNotResolve) {
6661        return null;
6662      } else {
6663        throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getElement().getId(), profile.getUrl());
6664      }
6665    } else {
6666      // 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
6667      // then we don't do that. .resolve() is allowed on the Reference.reference, but the target of the reference will be defined
6668      // on the Reference, not the reference.reference.
6669      ExpressionNode next = expr.getInner();
6670      if (dontWalkIntoReferences && focus.hasType("Reference") && next != null && next.getKind() == Kind.Name && next.getName().equals("reference")) {
6671        next = next.getInner();
6672      }
6673      if (next == null) {
6674        return focus;
6675      } else {
6676        return evaluateDefinition(next, sd, focus, profile, dontWalkIntoReferences);
6677      }
6678    }
6679  }
6680
6681  private ElementDefinition pickMandatorySlice(StructureDefinition sd, ElementDefinition element) throws DefinitionException {
6682    List<ElementDefinition> list = profileUtilities.getSliceList(sd, element);
6683    for (ElementDefinition ed : list) {
6684      if (ed.getMin() > 0) {
6685        return ed;
6686      }
6687    }
6688    return null;
6689  }
6690
6691
6692  private StructureDefinition fetchStructureByType(TypedElementDefinition ed, ExpressionNode expr) throws DefinitionException {
6693    if (ed.getTypes().size() == 0) {
6694      throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_NOTYPE, ed.getElement().getId());
6695    }
6696    if (ed.getTypes().size() > 1) {
6697      throw makeExceptionPlural(ed.getTypes().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES, ed.getElement().getId());
6698    }
6699    if (ed.getTypes().get(0).getProfile().size() > 1) {
6700      throw makeExceptionPlural(ed.getTypes().get(0).getProfile().size(), expr, I18nConstants.FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES, ed.getElement().getId());
6701    }
6702    if (ed.getTypes().get(0).hasProfile()) { 
6703      return worker.fetchResource(StructureDefinition.class, ed.getTypes().get(0).getProfile().get(0).getValue(), ed.getSrc());
6704    } else {
6705      return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getTypes().get(0).getCode(), null), ed.getSrc());
6706    }
6707  }
6708
6709
6710  private boolean tailMatches(ElementDefinition t, String d) {
6711    String tail = tailDot(t.getPath());
6712    if (d.contains("[")) {
6713      return tail.startsWith(d.substring(0, d.indexOf('[')));
6714    } else if (tail.equals(d)) {
6715      return true;
6716    } 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())) {
6717      return tail.startsWith(d);
6718    } else if (t.getPath().endsWith("[x]") && tail.startsWith(d)) {
6719      return true;
6720    }
6721    return false;
6722  }
6723
6724  private String tailDot(String path) {
6725    return path.substring(path.lastIndexOf(".") + 1);
6726  }
6727
6728  private Equality asBool(List<Base> items, ExpressionNode expr) throws PathEngineException {
6729    if (items.size() == 0) {
6730      return Equality.Null;
6731    } else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) {
6732      return asBool(items.get(0), true);
6733    } else if (items.size() == 1) {
6734      return Equality.True; 
6735    } else {
6736      throw makeException(expr, I18nConstants.FHIRPATH_UNABLE_BOOLEAN, convertToString(items));
6737    }
6738  }
6739
6740  private Equality asBoolFromInt(String s) {
6741    try {
6742      int i = Integer.parseInt(s);
6743      switch (i) {
6744      case 0: return Equality.False;
6745      case 1: return Equality.True;
6746      default: return Equality.Null;
6747      }
6748    } catch (Exception e) {
6749      return Equality.Null;
6750    }
6751  }
6752
6753  private Equality asBoolFromDec(String s) {
6754    try {
6755      BigDecimal d = new BigDecimal(s);
6756      if (d.compareTo(BigDecimal.ZERO) == 0) { 
6757        return Equality.False;
6758      } else if (d.compareTo(BigDecimal.ONE) == 0) { 
6759        return Equality.True;
6760      } else {
6761        return Equality.Null;
6762      }
6763    } catch (Exception e) {
6764      return Equality.Null;
6765    }
6766  }
6767
6768  private Equality asBool(Base item, boolean narrow) {
6769    if (item instanceof BooleanType) { 
6770      return boolToTriState(((BooleanType) item).booleanValue());
6771    } else if (item.isBooleanPrimitive()) {
6772      if (Utilities.existsInList(item.primitiveValue(), "true")) {
6773        return Equality.True;
6774      } else if (Utilities.existsInList(item.primitiveValue(), "false")) {
6775        return Equality.False;
6776      } else { 
6777        return Equality.Null;
6778      }
6779    } else if (narrow) {
6780      return Equality.False;
6781    } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt")) {
6782      return asBoolFromInt(item.primitiveValue());
6783    } else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal")) {
6784      return asBoolFromDec(item.primitiveValue());
6785    } else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) {
6786      if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y")) {
6787        return Equality.True;
6788      } else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n")) {
6789        return Equality.False;
6790      } else if (Utilities.isInteger(item.primitiveValue())) {
6791        return asBoolFromInt(item.primitiveValue());
6792      } else if (Utilities.isDecimal(item.primitiveValue(), true)) {
6793        return asBoolFromDec(item.primitiveValue());
6794      } else {
6795        return Equality.Null;
6796      }
6797    } 
6798    return Equality.Null;
6799  }
6800
6801  private Equality boolToTriState(boolean b) {
6802    return b ? Equality.True : Equality.False;
6803  }
6804
6805
6806  public ValidationOptions getTerminologyServiceOptions() {
6807    return terminologyServiceOptions;
6808  }
6809
6810
6811  public IWorkerContext getWorker() {
6812    return worker;
6813  }
6814
6815  public boolean isAllowPolymorphicNames() {
6816    return allowPolymorphicNames;
6817  }
6818
6819  public void setAllowPolymorphicNames(boolean allowPolymorphicNames) {
6820    this.allowPolymorphicNames = allowPolymorphicNames;
6821  }
6822
6823  public boolean isLiquidMode() {
6824    return liquidMode;
6825  }
6826
6827  public void setLiquidMode(boolean liquidMode) {
6828    this.liquidMode = liquidMode;
6829  }
6830
6831  public ProfileUtilities getProfileUtilities() {
6832    return profileUtilities;
6833  }
6834
6835  public boolean isAllowDoubleQuotes() {
6836    return allowDoubleQuotes;
6837  }
6838  public void setAllowDoubleQuotes(boolean allowDoubleQuotes) {
6839    this.allowDoubleQuotes = allowDoubleQuotes;    
6840  }
6841
6842  public boolean isEmitSQLonFHIRWarning() {
6843    return emitSQLonFHIRWarning;
6844  }
6845
6846  public void setEmitSQLonFHIRWarning(boolean emitSQLonFHIRWarning) {
6847    this.emitSQLonFHIRWarning = emitSQLonFHIRWarning;
6848  }
6849  
6850}