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