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