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