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