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