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