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