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