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