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