001package org.hl7.fhir.r4.utils;
002
003import java.math.BigDecimal;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.EnumSet;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012import java.util.TimeZone;
013
014import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
015import org.apache.commons.lang3.NotImplementedException;
016import org.fhir.ucum.Decimal;
017import org.fhir.ucum.Pair;
018import org.fhir.ucum.UcumException;
019import org.hl7.fhir.exceptions.DefinitionException;
020import org.hl7.fhir.exceptions.FHIRException;
021import org.hl7.fhir.exceptions.PathEngineException;
022import org.hl7.fhir.r4.conformance.ProfileUtilities;
023import org.hl7.fhir.r4.context.IWorkerContext;
024import org.hl7.fhir.r4.model.Base;
025import org.hl7.fhir.r4.model.BaseDateTimeType;
026import org.hl7.fhir.r4.model.BooleanType;
027import org.hl7.fhir.r4.model.DateTimeType;
028import org.hl7.fhir.r4.model.DateType;
029import org.hl7.fhir.r4.model.DecimalType;
030import org.hl7.fhir.r4.model.Element;
031import org.hl7.fhir.r4.model.ElementDefinition;
032import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
033import org.hl7.fhir.r4.model.ExpressionNode;
034import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus;
035import org.hl7.fhir.r4.model.ExpressionNode.Function;
036import org.hl7.fhir.r4.model.ExpressionNode.Kind;
037import org.hl7.fhir.r4.model.ExpressionNode.Operation;
038import org.hl7.fhir.r4.model.ExpressionNode.SourceLocation;
039import org.hl7.fhir.r4.model.IntegerType;
040import org.hl7.fhir.r4.model.Property;
041import org.hl7.fhir.r4.model.Quantity;
042import org.hl7.fhir.r4.model.Resource;
043import org.hl7.fhir.r4.model.StringType;
044import org.hl7.fhir.r4.model.StructureDefinition;
045import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
046import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
047import org.hl7.fhir.r4.model.TimeType;
048import org.hl7.fhir.r4.model.TypeDetails;
049import org.hl7.fhir.r4.model.TypeDetails.ProfiledType;
050import org.hl7.fhir.r4.model.ValueSet;
051import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException;
052import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
053import org.hl7.fhir.utilities.Utilities;
054
055/*-
056 * #%L
057 * org.hl7.fhir.r4
058 * %%
059 * Copyright (C) 2014 - 2019 Health Level 7
060 * %%
061 * Licensed under the Apache License, Version 2.0 (the "License");
062 * you may not use this file except in compliance with the License.
063 * You may obtain a copy of the License at
064 * 
065 *      http://www.apache.org/licenses/LICENSE-2.0
066 * 
067 * Unless required by applicable law or agreed to in writing, software
068 * distributed under the License is distributed on an "AS IS" BASIS,
069 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
070 * See the License for the specific language governing permissions and
071 * limitations under the License.
072 * #L%
073 */
074
075//import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
076import ca.uhn.fhir.util.ElementUtil;
077
078/**
079 * 
080 * @author Grahame Grieve
081 *
082 */
083public class FHIRPathEngine {
084  private enum Equality { Null, True, False }
085
086  private class FHIRConstant extends Base {
087
088    private static final long serialVersionUID = -8933773658248269439L;
089    private String value;
090
091    public FHIRConstant(String value) {
092      this.value = value;
093    }
094
095    @Override
096    public String fhirType() {
097      return "%constant";
098    }
099
100    @Override
101    protected void listChildren(List<Property> result) {
102    }
103
104    @Override
105    public String getIdBase() {
106      return null;
107    }
108
109    @Override
110    public void setIdBase(String value) {
111    }
112
113    public String getValue() {
114      return value;
115    }
116  }
117  
118  private class ClassTypeInfo extends Base {
119    private static final long serialVersionUID = 4909223114071029317L;
120    private Base instance;
121
122    public ClassTypeInfo(Base instance) {
123      super();
124      this.instance = instance;
125    }
126
127    @Override
128    public String fhirType() {
129      return "ClassInfo";
130    }
131
132    @Override
133    protected void listChildren(List<Property> result) {
134    }
135
136    @Override
137    public String getIdBase() {
138      return null;
139    }
140
141    @Override
142    public void setIdBase(String value) {
143    }
144    public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
145      if (name.equals("name")) 
146        return new Base[]{new StringType(getName())};
147      else if (name.equals("namespace")) 
148        return new Base[]{new StringType(getNamespace())};
149      else
150        return super.getProperty(hash, name, checkValid);
151    }
152
153    private String getNamespace() {
154      if ((instance instanceof Resource))
155        return "FHIR";
156      else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions())
157        return "System";
158      else
159        return "FHIR";
160    }
161
162    private String getName() {
163      if ((instance instanceof Resource))
164        return instance.fhirType();
165      else if (!(instance instanceof Element) || ((Element)instance).isDisallowExtensions())
166        return Utilities.capitalize(instance.fhirType());
167      else
168        return instance.fhirType();
169    }
170  }
171
172  private IWorkerContext worker;
173  private IEvaluationContext hostServices;
174  private StringBuilder log = new StringBuilder();
175  private Set<String> primitiveTypes = new HashSet<String>();
176  private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
177  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  
178
179  // if the fhir path expressions are allowed to use constants beyond those defined in the specification
180  // the application can implement them by providing a constant resolver 
181  public interface IEvaluationContext {
182    public class FunctionDetails {
183      private String description;
184      private int minParameters;
185      private int maxParameters;
186      public FunctionDetails(String description, int minParameters, int maxParameters) {
187        super();
188        this.description = description;
189        this.minParameters = minParameters;
190        this.maxParameters = maxParameters;
191      }
192      public String getDescription() {
193        return description;
194      }
195      public int getMinParameters() {
196        return minParameters;
197      }
198      public int getMaxParameters() {
199        return maxParameters;
200      }
201
202    }
203
204    /**
205     * A constant reference - e.g. a reference to a name that must be resolved in context.
206     * The % will be removed from the constant name before this is invoked.
207     * 
208     * This will also be called if the host invokes the FluentPath engine with a context of null
209     *  
210     * @param appContext - content passed into the fluent path engine
211     * @param name - name reference to resolve
212     * @param beforeContext - whether this is being called before the name is resolved locally, or not
213     * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
214     */
215    public Base resolveConstant(Object appContext, String name, boolean beforeContext)  throws PathEngineException;
216    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException;
217    
218    /**
219     * when the .log() function is called
220     * 
221     * @param argument
222     * @param focus
223     * @return
224     */
225    public boolean log(String argument, List<Base> focus);
226
227    // extensibility for functions
228    /**
229     * 
230     * @param functionName
231     * @return null if the function is not known
232     */
233    public FunctionDetails resolveFunction(String functionName);
234    
235    /**
236     * Check the function parameters, and throw an error if they are incorrect, or return the type for the function
237     * @param functionName
238     * @param parameters
239     * @return
240     */
241    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException;
242    
243    /**
244     * @param appContext
245     * @param functionName
246     * @param parameters
247     * @return
248     */
249    public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters);
250    
251    /**
252     * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
253     * @param url
254     * @return
255     * @throws FHIRException 
256     */
257    public Base resolveReference(Object appContext, String url) throws FHIRException;
258    
259    public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException;
260  }
261
262
263  /**
264   * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
265   */
266  public FHIRPathEngine(IWorkerContext worker) {
267    super();
268    this.worker = worker;
269    for (StructureDefinition sd : worker.allStructures()) {
270      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL)
271        allTypes.put(sd.getName(), sd);
272      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
273        primitiveTypes.add(sd.getName());
274      }
275    }
276  }
277
278
279  // --- 3 methods to override in children -------------------------------------------------------
280  // if you don't override, it falls through to the using the base reference implementation 
281  // HAPI overrides to these to support extending the base model
282
283  public IEvaluationContext getHostServices() {
284    return hostServices;
285  }
286
287
288  public void setHostServices(IEvaluationContext constantResolver) {
289    this.hostServices = constantResolver;
290  }
291
292
293  /**
294   * Given an item, return all the children that conform to the pattern described in name
295   * 
296   * Possible patterns:
297   *  - a simple name (which may be the base of a name with [] e.g. value[x])
298   *  - a name with a type replacement e.g. valueCodeableConcept
299   *  - * which means all children
300   *  - ** which means all descendants
301   *  
302   * @param item
303   * @param name
304   * @param result
305         * @throws FHIRException 
306   */
307  protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
308        Base[] list = item.listChildrenByName(name, false);
309        if (list != null)
310                for (Base v : list)
311      if (v != null)
312        result.add(v);
313  }
314
315  
316  public boolean isLegacyMode() {
317    return legacyMode;
318  }
319
320
321  public void setLegacyMode(boolean legacyMode) {
322    this.legacyMode = legacyMode;
323  }
324
325
326  // --- public API -------------------------------------------------------
327  /**
328   * Parse a path for later use using execute
329   * 
330   * @param path
331   * @return
332   * @throws PathEngineException 
333   * @throws Exception
334   */
335  public ExpressionNode parse(String path) throws FHIRLexerException {
336    return parse(path, null);
337  }
338  
339  public ExpressionNode parse(String path, String name) throws FHIRLexerException {
340    FHIRLexer lexer = new FHIRLexer(path, name);
341    if (lexer.done())
342      throw lexer.error("Path cannot be empty");
343    ExpressionNode result = parseExpression(lexer, true);
344    if (!lexer.done())
345      throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\"");
346    result.check();
347    return result;    
348  }
349
350  public static class ExpressionNodeWithOffset {
351    private int offset;
352    private ExpressionNode node;
353    public ExpressionNodeWithOffset(int offset, ExpressionNode node) {
354      super();
355      this.offset = offset;
356      this.node = node;
357    }
358    public int getOffset() {
359      return offset;
360    }
361    public ExpressionNode getNode() {
362      return node;
363    }
364    
365  }
366  /**
367   * Parse a path for later use using execute
368   * 
369   * @param path
370   * @return
371   * @throws PathEngineException 
372   * @throws Exception
373   */
374  public ExpressionNodeWithOffset parsePartial(String path, int i) throws FHIRLexerException {
375    FHIRLexer lexer = new FHIRLexer(path, i);
376    if (lexer.done())
377      throw lexer.error("Path cannot be empty");
378    ExpressionNode result = parseExpression(lexer, true);
379    result.check();
380    return new ExpressionNodeWithOffset(lexer.getCurrentStart(), result);    
381  }
382
383  /**
384   * Parse a path that is part of some other syntax
385   *  
386   * @return
387   * @throws PathEngineException 
388   * @throws Exception
389   */
390  public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException {
391    ExpressionNode result = parseExpression(lexer, true);
392    result.check();
393    return result;    
394  }
395
396  /**
397   * check that paths referred to in the ExpressionNode are valid
398   * 
399   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
400   * 
401   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
402   * 
403   * @param context - the logical type against which this path is applied
404   * @throws DefinitionException
405   * @throws PathEngineException 
406   * @if the path is not valid
407   */
408  public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
409    // if context is a path that refers to a type, do that conversion now 
410        TypeDetails types; 
411        if (context == null) {
412          types = null; // this is a special case; the first path reference will have to resolve to something in the context
413        } else if (!context.contains(".")) {
414    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context);
415          types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
416        } else {
417          String ctxt = context.substring(0, context.indexOf('.'));
418      if (Utilities.isAbsoluteUrl(resourceType)) {
419        ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt;
420      }
421          StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt);
422          if (sd == null) 
423            throw new PathEngineException("Unknown context "+context);
424          ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
425          if (ed == null) 
426            throw new PathEngineException("Unknown context element "+context);
427          if (ed.fixedType != null) 
428            types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
429          else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 
430            types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context);
431          else {
432            types = new TypeDetails(CollectionStatus.SINGLETON);
433                for (TypeRefComponent t : ed.getDefinition().getType()) 
434                  types.addType(t.getCode());
435          }
436        }
437
438    return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true);
439  }
440
441  public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
442    // if context is a path that refers to a type, do that conversion now 
443    TypeDetails types; 
444    if (!context.contains(".")) {
445      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
446    } else {
447      ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
448      if (ed == null) 
449        throw new PathEngineException("Unknown context element "+context);
450      if (ed.fixedType != null) 
451        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
452      else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) 
453        types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context);
454      else {
455        types = new TypeDetails(CollectionStatus.SINGLETON);
456        for (TypeRefComponent t : ed.getDefinition().getType()) 
457          types.addType(t.getCode());
458      }
459    }
460
461    return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true);
462  }
463
464  public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
465    // if context is a path that refers to a type, do that conversion now 
466    TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
467    return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true);
468  }
469
470  public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
471    return check(appContext, resourceType, context, parse(expr));
472  }
473
474  private int compareDateTimeElements(Base theL, Base theR, boolean theEquivalenceTest) {
475    String dateLeftString = theL.primitiveValue();
476    DateTimeType dateLeft = new DateTimeType(dateLeftString);
477
478    String dateRightString = theR.primitiveValue();
479    DateTimeType dateRight = new DateTimeType(dateRightString);
480
481    if (theEquivalenceTest) {
482      return dateLeft.equalsUsingFhirPathRules(dateRight) == Boolean.TRUE ? 0 : 1;
483    }
484
485    if (dateLeft.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
486      dateLeft.setTimeZoneZulu(true);
487    }
488    if (dateRight.getPrecision().ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
489      dateRight.setTimeZoneZulu(true);
490    }
491
492    dateLeftString = dateLeft.getValueAsString();
493    dateRightString = dateRight.getValueAsString();
494
495    return dateLeftString.compareTo(dateRightString);
496  }
497
498  /**
499   * evaluate a path and return the matching elements
500   * 
501   * @param base - the object against which the path is being evaluated
502   * @param ExpressionNode - the parsed ExpressionNode statement to use
503   * @return
504   * @throws FHIRException 
505   * @
506   */
507        public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException {
508    List<Base> list = new ArrayList<Base>();
509    if (base != null)
510      list.add(base);
511    log = new StringBuilder();
512    return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true);
513  }
514
515  /**
516   * evaluate a path and return the matching elements
517   * 
518   * @param base - the object against which the path is being evaluated
519   * @param path - the FHIR Path statement to use
520   * @return
521         * @throws FHIRException 
522   * @
523   */
524        public List<Base> evaluate(Base base, String path) throws FHIRException {
525    ExpressionNode exp = parse(path);
526    List<Base> list = new ArrayList<Base>();
527    if (base != null)
528      list.add(base);
529    log = new StringBuilder();
530    return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true);
531  }
532
533  /**
534   * evaluate a path and return the matching elements
535   * 
536   * @param base - the object against which the path is being evaluated
537   * @param ExpressionNode - the parsed ExpressionNode statement to use
538   * @return
539         * @throws FHIRException 
540   * @
541   */
542        public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
543    List<Base> list = new ArrayList<Base>();
544    if (base != null)
545      list.add(base);
546    log = new StringBuilder();
547    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
548  }
549
550  /**
551   * evaluate a path and return the matching elements
552   * 
553   * @param base - the object against which the path is being evaluated
554   * @param ExpressionNode - the parsed ExpressionNode statement to use
555   * @return
556   * @throws FHIRException 
557   * @
558   */
559  public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
560    List<Base> list = new ArrayList<Base>();
561    if (base != null)
562      list.add(base);
563    log = new StringBuilder();
564    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
565  }
566
567  /**
568   * evaluate a path and return the matching elements
569   * 
570   * @param base - the object against which the path is being evaluated
571   * @param path - the FHIR Path statement to use
572   * @return
573         * @throws FHIRException 
574   * @
575   */
576        public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException {
577    ExpressionNode exp = parse(path);
578    List<Base> list = new ArrayList<Base>();
579    if (base != null)
580      list.add(base);
581    log = new StringBuilder();
582    return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true);
583  }
584
585  /**
586   * evaluate a path and return true or false (e.g. for an invariant)
587   * 
588   * @param base - the object against which the path is being evaluated
589   * @param path - the FHIR Path statement to use
590   * @return
591         * @throws FHIRException 
592   * @
593   */
594        public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException {
595    return convertToBoolean(evaluate(null, resource, base, path));
596  }
597
598  /**
599   * evaluate a path and return true or false (e.g. for an invariant)
600   * 
601   * @param base - the object against which the path is being evaluated
602   * @return
603   * @throws FHIRException 
604   * @
605   */
606  public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException {
607    return convertToBoolean(evaluate(null, resource, base, node));
608  }
609
610  /**
611   * evaluate a path and return true or false (e.g. for an invariant)
612   * 
613   * @param appInfo - application context
614   * @param base - the object against which the path is being evaluated
615   * @return
616   * @throws FHIRException 
617   * @
618   */
619  public boolean evaluateToBoolean(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException {
620    return convertToBoolean(evaluate(appInfo, resource, base, node));
621  }
622
623  /**
624   * evaluate a path and return true or false (e.g. for an invariant)
625   * 
626   * @param base - the object against which the path is being evaluated
627   * @return
628   * @throws FHIRException 
629   * @
630   */
631  public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException {
632    return convertToBoolean(evaluate(null, resource, base, node));
633  }
634
635  /**
636   * evaluate a path and a string containing the outcome (for display)
637   * 
638   * @param base - the object against which the path is being evaluated
639   * @param path - the FHIR Path statement to use
640   * @return
641         * @throws FHIRException 
642   * @
643   */
644  public String evaluateToString(Base base, String path) throws FHIRException {
645    return convertToString(evaluate(base, path));
646  }
647
648  public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException {
649    return convertToString(evaluate(appInfo, resource, base, node));
650  }
651
652  /**
653   * worker routine for converting a set of objects to a string representation
654   * 
655   * @param items - result from @evaluate
656   * @return
657   */
658  public String convertToString(List<Base> items) {
659    StringBuilder b = new StringBuilder();
660    boolean first = true;
661    for (Base item : items) {
662      if (first) 
663        first = false;
664      else
665        b.append(',');
666
667      b.append(convertToString(item));
668    }
669    return b.toString();
670  }
671
672  public String convertToString(Base item) {
673    if (item.isPrimitive())
674      return item.primitiveValue();
675    else if (item instanceof Quantity) {
676      Quantity q = (Quantity) item;
677      if (q.getSystem().equals("http://unitsofmeasure.org")) {
678        String u = "'"+q.getCode()+"'";
679        return q.getValue().toPlainString()+" "+u;
680      }
681      else
682        return item.toString();
683    } else
684      return item.toString();
685  }
686
687  /**
688   * worker routine for converting a set of objects to a boolean representation (for invariants)
689   * 
690   * @param items - result from @evaluate
691   * @return
692   */
693  public boolean convertToBoolean(List<Base> items) {
694    if (items == null)
695      return false;
696    else if (items.size() == 1 && items.get(0) instanceof BooleanType)
697      return ((BooleanType) items.get(0)).getValue();
698    else if (items.size() == 1 && items.get(0).isBooleanPrimitive()) // element model
699      return Boolean.valueOf(items.get(0).primitiveValue());
700    else 
701      return items.size() > 0;
702  }
703
704
705  private void log(String name, List<Base> contents) {
706    if (hostServices == null || !hostServices.log(name, contents)) {
707      if (log.length() > 0)
708        log.append("; ");
709      log.append(name);
710      log.append(": ");
711      boolean first = true;
712      for (Base b : contents) {
713        if (first)
714          first = false;
715        else
716          log.append(",");
717        log.append(convertToString(b));
718      }
719    }
720  }
721
722  public String forLog() {
723    if (log.length() > 0)
724      return " ("+log.toString()+")";
725    else
726      return "";
727  }
728
729  private class ExecutionContext {
730    private Object appInfo;
731    private Base resource;
732    private Base context;
733    private Base thisItem;
734    private List<Base> total;
735    private Map<String, Base> aliases;
736    
737    public ExecutionContext(Object appInfo, Base resource, Base context, Map<String, Base> aliases, Base thisItem) {
738      this.appInfo = appInfo;
739      this.context = context;
740      this.resource = resource; 
741      this.aliases = aliases;
742      this.thisItem = thisItem;
743    }
744    public Base getResource() {
745      return resource;
746    }
747    public Base getThisItem() {
748      return thisItem;
749    }
750    public List<Base> getTotal() {
751      return total;
752    }
753    public void addAlias(String name, List<Base> focus) throws FHIRException {
754      if (aliases == null)
755        aliases = new HashMap<String, Base>();
756      else
757        aliases = new HashMap<String, Base>(aliases); // clone it, since it's going to change 
758      if (focus.size() > 1)
759        throw new FHIRException("Attempt to alias a collection, not a singleton");
760      aliases.put(name, focus.size() == 0 ? null : focus.get(0));      
761    }
762    public Base getAlias(String name) {
763      return aliases == null ? null : aliases.get(name);
764    }
765  }
766
767  private class ExecutionTypeContext {
768    private Object appInfo; 
769    private String resource;
770    private TypeDetails context;
771    private TypeDetails thisItem;
772    private TypeDetails total;
773
774
775    public ExecutionTypeContext(Object appInfo, String resource, TypeDetails context, TypeDetails thisItem) {
776      super();
777      this.appInfo = appInfo;
778      this.resource = resource;
779      this.context = context;
780      this.thisItem = thisItem;
781      
782    }
783    public String getResource() {
784      return resource;
785    }
786    public TypeDetails getThisItem() {
787      return thisItem;
788    }
789
790    
791  }
792
793  private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
794    ExpressionNode result = new ExpressionNode(lexer.nextId());
795    ExpressionNode wrapper = null;
796    SourceLocation c = lexer.getCurrentStartLocation();
797    result.setStart(lexer.getCurrentLocation());
798    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
799    // so we back correct for both +/- and as part of a numeric constant below.
800    
801    // special: +/- represents a unary operation at this point, but cannot be a feature of the lexer, since that's not always true.
802    // so we back correct for both +/- and as part of a numeric constant below.
803    if (Utilities.existsInList(lexer.getCurrent(), "-", "+")) {
804      wrapper = new ExpressionNode(lexer.nextId());
805      wrapper.setKind(Kind.Unary);
806      wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.take()));
807      wrapper.setProximal(proximal);
808    }
809
810    if (lexer.isConstant()) {
811      boolean isString = lexer.isStringConstant();
812      if (!isString && (lexer.getCurrent().startsWith("-") || lexer.getCurrent().startsWith("+"))) {
813        // the grammar says that this is a unary operation; it affects the correct processing order of the inner operations
814        wrapper = new ExpressionNode(lexer.nextId());
815        wrapper.setKind(Kind.Unary);
816        wrapper.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent().substring(0, 1)));
817        wrapper.setProximal(proximal);
818        lexer.setCurrent(lexer.getCurrent().substring(1));
819      }
820      result.setConstant(processConstant(lexer));
821      result.setKind(Kind.Constant);
822      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"))) {
823        // it's a quantity
824        String ucum = null;
825        if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) {
826          String s = lexer.take();
827          if (s.equals("year") || s.equals("years"))
828            ucum = "a";
829          else if (s.equals("month") || s.equals("months"))
830            ucum = "mo";
831          else if (s.equals("week") || s.equals("weeks"))
832            ucum = "wk";
833          else if (s.equals("day") || s.equals("days"))
834            ucum = "d";
835          else if (s.equals("hour") || s.equals("hours"))
836            ucum = "h";
837          else if (s.equals("minute") || s.equals("minutes"))
838            ucum = "min";
839          else if (s.equals("second") || s.equals("seconds"))
840            ucum = "s";
841          else // (s.equals("millisecond") || s.equals("milliseconds"))
842            ucum = "ms";
843        } else 
844          ucum = lexer.readConstant("units");
845        result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum));
846      }
847      result.setEnd(lexer.getCurrentLocation());
848    } else if ("(".equals(lexer.getCurrent())) {
849      lexer.next();
850      result.setKind(Kind.Group);
851      result.setGroup(parseExpression(lexer, true));
852      if (!")".equals(lexer.getCurrent())) 
853        throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\"");
854      result.setEnd(lexer.getCurrentLocation());
855      lexer.next();
856    } else {
857      if (!lexer.isToken() && !lexer.getCurrent().startsWith("`")) 
858        throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name");
859      if (lexer.isFixedName())
860        result.setName(lexer.readFixedName("Path Name"));
861      else
862        result.setName(lexer.take());
863      result.setEnd(lexer.getCurrentLocation());
864      if (!result.checkName())
865        throw lexer.error("Found "+result.getName()+" expecting a valid token name");
866      if ("(".equals(lexer.getCurrent())) {
867        Function f = Function.fromCode(result.getName());
868        FunctionDetails details = null;
869        if (f == null) {
870          if (hostServices != null)
871            details = hostServices.resolveFunction(result.getName());
872          if (details == null)
873            throw lexer.error("The name "+result.getName()+" is not a valid function name");
874          f = Function.Custom;
875        }
876        result.setKind(Kind.Function);
877        result.setFunction(f);
878        lexer.next();
879        while (!")".equals(lexer.getCurrent())) { 
880          result.getParameters().add(parseExpression(lexer, true));
881          if (",".equals(lexer.getCurrent()))
882            lexer.next();
883          else if (!")".equals(lexer.getCurrent()))
884            throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected");
885        }
886        result.setEnd(lexer.getCurrentLocation());
887        lexer.next();
888        checkParameters(lexer, c, result, details);
889      } else
890        result.setKind(Kind.Name);
891    }
892    ExpressionNode focus = result;
893    if ("[".equals(lexer.getCurrent())) {
894      lexer.next();
895      ExpressionNode item = new ExpressionNode(lexer.nextId());
896      item.setKind(Kind.Function);
897      item.setFunction(ExpressionNode.Function.Item);
898      item.getParameters().add(parseExpression(lexer, true));
899      if (!lexer.getCurrent().equals("]"))
900        throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected");
901      lexer.next();
902      result.setInner(item);
903      focus = item;
904    }
905    if (".".equals(lexer.getCurrent())) {
906      lexer.next();
907      focus.setInner(parseExpression(lexer, false));
908    }
909    result.setProximal(proximal);
910    if (proximal) {
911      while (lexer.isOp()) {
912        focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent()));
913        focus.setOpStart(lexer.getCurrentStartLocation());
914        focus.setOpEnd(lexer.getCurrentLocation());
915        lexer.next();
916        focus.setOpNext(parseExpression(lexer, false));
917        focus = focus.getOpNext();
918      }
919      result = organisePrecedence(lexer, result);
920    }
921    if (wrapper != null) {
922      wrapper.setOpNext(result);
923      result.setProximal(false);
924      result = wrapper;
925    }
926    return result;
927  }
928
929  private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) {
930    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod)); 
931    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate)); 
932    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union)); 
933    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThan, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual));
934    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is));
935    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
936    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
937    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
938    // last: implies
939    return node;
940  }
941
942  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
943    //    work : boolean;
944    //    focus, node, group : ExpressionNode;
945
946    assert(start.isProximal());
947
948    // is there anything to do?
949    boolean work = false;
950    ExpressionNode focus = start.getOpNext();
951    if (ops.contains(start.getOperation())) {
952      while (focus != null && focus.getOperation() != null) {
953        work = work || !ops.contains(focus.getOperation());
954        focus = focus.getOpNext();
955      }
956    } else {
957      while (focus != null && focus.getOperation() != null) {
958        work = work || ops.contains(focus.getOperation());
959        focus = focus.getOpNext();
960      }
961    }  
962    if (!work)
963      return start;
964
965    // entry point: tricky
966    ExpressionNode group;
967    if (ops.contains(start.getOperation())) {
968      group = newGroup(lexer, start);
969      group.setProximal(true);
970      focus = start;
971      start = group;
972    } else {
973      ExpressionNode node = start;
974
975      focus = node.getOpNext();
976      while (!ops.contains(focus.getOperation())) {
977        node = focus;
978        focus = focus.getOpNext();
979      }
980      group = newGroup(lexer, focus);
981      node.setOpNext(group);
982    }
983
984    // now, at this point:
985    //   group is the group we are adding to, it already has a .group property filled out.
986    //   focus points at the group.group
987    do {
988      // run until we find the end of the sequence
989      while (ops.contains(focus.getOperation()))
990        focus = focus.getOpNext();
991      if (focus.getOperation() != null) {
992        group.setOperation(focus.getOperation());
993        group.setOpNext(focus.getOpNext());
994        focus.setOperation(null);
995        focus.setOpNext(null);
996        // now look for another sequence, and start it
997        ExpressionNode node = group;
998        focus = group.getOpNext();
999        if (focus != null) { 
1000          while (focus != null && !ops.contains(focus.getOperation())) {
1001            node = focus;
1002            focus = focus.getOpNext();
1003          }
1004          if (focus != null) { // && (focus.Operation in Ops) - must be true 
1005            group = newGroup(lexer, focus);
1006            node.setOpNext(group);
1007          }
1008        }
1009      }
1010    }
1011    while (focus != null && focus.getOperation() != null);
1012    return start;
1013  }
1014
1015
1016  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
1017    ExpressionNode result = new ExpressionNode(lexer.nextId());
1018    result.setKind(Kind.Group);
1019    result.setGroup(next);
1020    result.getGroup().setProximal(true);
1021    return result;
1022  }
1023
1024  private Base processConstant(FHIRLexer lexer) throws FHIRLexerException {
1025    if (lexer.isStringConstant()) {
1026      return new StringType(processConstantString(lexer.take(), lexer)).noExtensions();
1027    } else if (Utilities.isInteger(lexer.getCurrent())) {
1028      return new IntegerType(lexer.take()).noExtensions();
1029    } else if (Utilities.isDecimal(lexer.getCurrent())) {
1030      return new DecimalType(lexer.take()).noExtensions();
1031    } else if (Utilities.existsInList(lexer.getCurrent(), "true", "false")) {
1032      return new BooleanType(lexer.take()).noExtensions();
1033    } else if (lexer.getCurrent().equals("{}")) {
1034      lexer.take();
1035      return null;
1036    } else if (lexer.getCurrent().startsWith("%") || lexer.getCurrent().startsWith("@")) {
1037      return new FHIRConstant(lexer.take());
1038    } else
1039      throw lexer.error("Invalid Constant "+lexer.getCurrent());
1040  }
1041
1042  //  procedure CheckParamCount(c : integer);
1043  //  begin
1044  //    if exp.Parameters.Count <> c then
1045  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
1046  //  end;
1047
1048  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
1049    if (exp.getParameters().size() != count)
1050      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString());
1051    return true;
1052  }
1053
1054  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
1055    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax)
1056      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString());
1057    return true;
1058  }
1059
1060  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
1061    switch (exp.getFunction()) {
1062    case Empty: return checkParamCount(lexer, location, exp, 0);
1063    case Not: return checkParamCount(lexer, location, exp, 0);
1064    case Exists: return checkParamCount(lexer, location, exp, 0);
1065    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
1066    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
1067    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
1068    case Distinct: return checkParamCount(lexer, location, exp, 0);
1069    case Count: return checkParamCount(lexer, location, exp, 0);
1070    case Where: return checkParamCount(lexer, location, exp, 1);
1071    case Select: return checkParamCount(lexer, location, exp, 1);
1072    case All: return checkParamCount(lexer, location, exp, 0, 1);
1073    case Repeat: return checkParamCount(lexer, location, exp, 1);
1074    case Aggregate: return checkParamCount(lexer, location, exp, 1, 2);
1075    case Item: return checkParamCount(lexer, location, exp, 1);
1076    case As: return checkParamCount(lexer, location, exp, 1);
1077    case OfType: return checkParamCount(lexer, location, exp, 1);
1078    case Type: return checkParamCount(lexer, location, exp, 0);
1079    case Is: return checkParamCount(lexer, location, exp, 1);
1080    case Single: return checkParamCount(lexer, location, exp, 0);
1081    case First: return checkParamCount(lexer, location, exp, 0);
1082    case Last: return checkParamCount(lexer, location, exp, 0);
1083    case Tail: return checkParamCount(lexer, location, exp, 0);
1084    case Skip: return checkParamCount(lexer, location, exp, 1);
1085    case Take: return checkParamCount(lexer, location, exp, 1);
1086    case Union: return checkParamCount(lexer, location, exp, 1);
1087    case Combine: return checkParamCount(lexer, location, exp, 1);
1088    case Intersect: return checkParamCount(lexer, location, exp, 1);
1089    case Exclude: return checkParamCount(lexer, location, exp, 1);
1090    case Iif: return checkParamCount(lexer, location, exp, 2,3);
1091    case Lower: return checkParamCount(lexer, location, exp, 0);
1092    case Upper: return checkParamCount(lexer, location, exp, 0);
1093    case ToChars: return checkParamCount(lexer, location, exp, 0);
1094    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
1095    case StartsWith: return checkParamCount(lexer, location, exp, 1);
1096    case EndsWith: return checkParamCount(lexer, location, exp, 1);
1097    case Matches: return checkParamCount(lexer, location, exp, 1);
1098    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
1099    case Contains: return checkParamCount(lexer, location, exp, 1);
1100    case Replace: return checkParamCount(lexer, location, exp, 2);
1101    case Length: return checkParamCount(lexer, location, exp, 0);
1102    case Children: return checkParamCount(lexer, location, exp, 0);
1103    case Descendants: return checkParamCount(lexer, location, exp, 0);
1104    case MemberOf: return checkParamCount(lexer, location, exp, 1);
1105    case Trace: return checkParamCount(lexer, location, exp, 1, 2);
1106    case Today: return checkParamCount(lexer, location, exp, 0);
1107    case Now: return checkParamCount(lexer, location, exp, 0);
1108    case Resolve: return checkParamCount(lexer, location, exp, 0);
1109    case Extension: return checkParamCount(lexer, location, exp, 1);
1110    case AllFalse: return checkParamCount(lexer, location, exp, 0);
1111    case AnyFalse: return checkParamCount(lexer, location, exp, 0);
1112    case AllTrue: return checkParamCount(lexer, location, exp, 0);
1113    case AnyTrue: return checkParamCount(lexer, location, exp, 0);
1114    case HasValue: return checkParamCount(lexer, location, exp, 0);
1115    case Alias: return checkParamCount(lexer, location, exp, 1);
1116    case AliasAs: return checkParamCount(lexer, location, exp, 1);
1117    case HtmlChecks: return checkParamCount(lexer, location, exp, 0);
1118    case ToInteger: return checkParamCount(lexer, location, exp, 0);
1119    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
1120    case ToString: return checkParamCount(lexer, location, exp, 0);
1121    case ToQuantity: return checkParamCount(lexer, location, exp, 0);
1122    case ToBoolean: return checkParamCount(lexer, location, exp, 0);
1123    case ToDateTime: return checkParamCount(lexer, location, exp, 0);
1124    case ToTime: return checkParamCount(lexer, location, exp, 0);
1125    case ConvertsToInteger: return checkParamCount(lexer, location, exp, 0);
1126    case ConvertsToDecimal: return checkParamCount(lexer, location, exp, 0);
1127    case ConvertsToString: return checkParamCount(lexer, location, exp, 0);
1128    case ConvertsToQuantity: return checkParamCount(lexer, location, exp, 0);
1129    case ConvertsToBoolean: return checkParamCount(lexer, location, exp, 0);
1130    case ConvertsToDateTime: return checkParamCount(lexer, location, exp, 0);
1131    case ConvertsToTime: return checkParamCount(lexer, location, exp, 0);
1132    case ConformsTo: return checkParamCount(lexer, location, exp, 1);
1133    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
1134    }
1135    return false;
1136  }
1137
1138        private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
1139//    System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
1140    List<Base> work = new ArrayList<Base>();
1141    switch (exp.getKind()) {
1142    case Unary:
1143      work.add(new IntegerType(0));
1144      break;
1145    case Name:
1146      if (atEntry && exp.getName().equals("$this"))
1147        work.add(context.getThisItem());
1148      else if (atEntry && exp.getName().equals("$total"))
1149        work.addAll(context.getTotal());
1150      else
1151        for (Base item : focus) {
1152          List<Base> outcome = execute(context, item, exp, atEntry);
1153          for (Base base : outcome)
1154            if (base != null)
1155              work.add(base);
1156        }                       
1157      break;
1158    case Function:
1159      List<Base> work2 = evaluateFunction(context, focus, exp);
1160      work.addAll(work2);
1161      break;
1162    case Constant:
1163      Base b = resolveConstant(context, exp.getConstant(), false);
1164      if (b != null)
1165        work.add(b);
1166      break;
1167    case Group:
1168      work2 = execute(context, focus, exp.getGroup(), atEntry);
1169      work.addAll(work2);
1170    }
1171
1172    if (exp.getInner() != null)
1173      work = execute(context, work, exp.getInner(), false);
1174
1175    if (exp.isProximal() && exp.getOperation() != null) {
1176      ExpressionNode next = exp.getOpNext();
1177      ExpressionNode last = exp;
1178      while (next != null) {
1179        List<Base> work2 = preOperate(work, last.getOperation());
1180        if (work2 != null)
1181          work = work2;
1182        else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
1183          work2 = executeTypeName(context, focus, next, false);
1184          work = operate(work, last.getOperation(), work2);
1185        } else {
1186          work2 = execute(context, focus, next, true);
1187          work = operate(work, last.getOperation(), work2);
1188//          System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString());
1189        }
1190        last = next;
1191        next = next.getOpNext();
1192      }
1193    }
1194//    System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString());
1195    return work;
1196  }
1197
1198  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
1199    List<Base> result = new ArrayList<Base>();
1200    if (next.getInner() != null)
1201      result.add(new StringType(next.getName()+"."+next.getInner().getName()));
1202    else 
1203      result.add(new StringType(next.getName()));
1204    return result;
1205  }
1206
1207
1208  private List<Base> preOperate(List<Base> left, Operation operation) throws PathEngineException {
1209    if (left.size() == 0)
1210      return null;
1211    switch (operation) {
1212    case And:
1213      return isBoolean(left, false) ? makeBoolean(false) : null;
1214    case Or:
1215      return isBoolean(left, true) ? makeBoolean(true) : null;
1216    case Implies:
1217      Equality v = asBool(left); 
1218      return v == Equality.False ? makeBoolean(true) : null;
1219    default: 
1220      return null;
1221    }
1222  }
1223
1224  private List<Base> makeBoolean(boolean b) {
1225    List<Base> res = new ArrayList<Base>();
1226    res.add(new BooleanType(b).noExtensions());
1227    return res;
1228  }
1229
1230  private List<Base> makeNull() {
1231    List<Base> res = new ArrayList<Base>();
1232    return res;
1233  }
1234
1235  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1236    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
1237  }
1238
1239  private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1240    TypeDetails result = new TypeDetails(null);
1241    switch (exp.getKind()) {
1242    case Name:
1243      if (atEntry && exp.getName().equals("$this"))
1244        result.update(context.getThisItem());
1245      else if (atEntry && exp.getName().equals("$total"))
1246        result.update(anything(CollectionStatus.UNORDERED));
1247      else if (atEntry && focus == null)
1248        result.update(executeContextType(context, exp.getName()));
1249      else {
1250        for (String s : focus.getTypes()) {
1251          result.update(executeType(s, exp, atEntry));
1252        }
1253        if (result.hasNoTypes()) 
1254          throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe());
1255      }
1256      break;
1257    case Function:
1258      result.update(evaluateFunctionType(context, focus, exp));
1259      break;
1260    case Unary:
1261      result.addType("integer");
1262      break;
1263    case Constant:
1264      result.update(resolveConstantType(context, exp.getConstant()));
1265      break;
1266    case Group:
1267      result.update(executeType(context, focus, exp.getGroup(), atEntry));
1268    }
1269    exp.setTypes(result);
1270
1271    if (exp.getInner() != null) {
1272      result = executeType(context, result, exp.getInner(), false);
1273    }
1274
1275    if (exp.isProximal() && exp.getOperation() != null) {
1276      ExpressionNode next = exp.getOpNext();
1277      ExpressionNode last = exp;
1278      while (next != null) {
1279        TypeDetails work;
1280        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As)
1281          work = executeTypeName(context, focus, next, atEntry);
1282        else
1283          work = executeType(context, focus, next, atEntry);
1284        result = operateTypes(result, last.getOperation(), work);
1285        last = next;
1286        next = next.getOpNext();
1287      }
1288      exp.setOpTypes(result);
1289    }
1290    return result;
1291  }
1292
1293  private Base resolveConstant(ExecutionContext context, Base constant, boolean beforeContext) throws PathEngineException {
1294    if (!(constant instanceof FHIRConstant))
1295      return constant;
1296    FHIRConstant c = (FHIRConstant) constant;
1297    if (c.getValue().startsWith("%")) {
1298      return resolveConstant(context, c.getValue(), beforeContext);
1299    } else if (c.getValue().startsWith("@")) {
1300      return processDateConstant(context.appInfo, c.getValue().substring(1));
1301    } else 
1302      throw new PathEngineException("Invaild FHIR Constant "+c.getValue());
1303  }
1304
1305  private Base processDateConstant(Object appInfo, String value) throws PathEngineException {
1306    if (value.startsWith("T"))
1307      return new TimeType(value.substring(1)).noExtensions();
1308    String v = value;
1309    if (v.length() > 10) {
1310      int i = v.substring(10).indexOf("-");
1311      if (i == -1)
1312        i = v.substring(10).indexOf("+");
1313      if (i == -1)
1314        i = v.substring(10).indexOf("Z");
1315      v = i == -1 ? value : v.substring(0,  10+i);
1316    }
1317    if (v.length() > 10)
1318      return new DateTimeType(value).noExtensions();
1319    else 
1320      return new DateType(value).noExtensions();
1321  }
1322
1323
1324  private Base resolveConstant(ExecutionContext context, String s, boolean beforeContext) throws PathEngineException {
1325    if (s.equals("%sct"))
1326      return new StringType("http://snomed.info/sct").noExtensions();
1327    else if (s.equals("%loinc"))
1328      return new StringType("http://loinc.org").noExtensions();
1329    else if (s.equals("%ucum"))
1330      return new StringType("http://unitsofmeasure.org").noExtensions();
1331    else if (s.equals("%resource")) {
1332      if (context.resource == null)
1333        throw new PathEngineException("Cannot use %resource in this context");
1334      return context.resource;
1335    } else if (s.equals("%context")) {
1336      return context.context;
1337    } else if (s.equals("%us-zip"))
1338      return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions();
1339    else if (s.startsWith("%`vs-"))
1340      return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions();
1341    else if (s.startsWith("%`cs-"))
1342      return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions();
1343    else if (s.startsWith("%`ext-"))
1344      return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions();
1345    else if (hostServices == null)
1346      throw new PathEngineException("Unknown fixed constant '"+s+"'");
1347    else
1348      return hostServices.resolveConstant(context.appInfo, s.substring(1), beforeContext);
1349  }
1350
1351
1352  private String processConstantString(String s, FHIRLexer lexer) throws FHIRLexerException {
1353    StringBuilder b = new StringBuilder();
1354    int i = 1;
1355    while (i < s.length()-1) {
1356      char ch = s.charAt(i);
1357      if (ch == '\\') {
1358        i++;
1359        switch (s.charAt(i)) {
1360        case 't': 
1361          b.append('\t');
1362          break;
1363        case 'r':
1364          b.append('\r');
1365          break;
1366        case 'n': 
1367          b.append('\n');
1368          break;
1369        case 'f': 
1370          b.append('\f');
1371          break;
1372        case '\'':
1373          b.append('\'');
1374          break;
1375        case '"':
1376          b.append('"');
1377          break;
1378        case '`':
1379          b.append('`');
1380          break;
1381        case '\\': 
1382          b.append('\\');
1383          break;
1384        case '/': 
1385          b.append('/');
1386          break;
1387        case 'u':
1388          i++;
1389          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1390          b.append((char) uc);
1391          i = i + 3;
1392          break;
1393        default:
1394          throw lexer.error("Unknown character escape \\"+s.charAt(i));
1395        }
1396        i++;
1397      } else {
1398        b.append(ch);
1399        i++;
1400      }
1401    }
1402    return b.toString();
1403  }
1404
1405
1406  private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException {
1407    switch (operation) {
1408    case Equals: return opEquals(left, right);
1409    case Equivalent: return opEquivalent(left, right);
1410    case NotEquals: return opNotEquals(left, right);
1411    case NotEquivalent: return opNotEquivalent(left, right);
1412    case LessThan: return opLessThan(left, right);
1413    case Greater: return opGreater(left, right);
1414    case LessOrEqual: return opLessOrEqual(left, right);
1415    case GreaterOrEqual: return opGreaterOrEqual(left, right);
1416    case Union: return opUnion(left, right);
1417    case In: return opIn(left, right);
1418    case MemberOf: return opMemberOf(left, right);
1419    case Contains: return opContains(left, right);
1420    case Or:  return opOr(left, right);
1421    case And:  return opAnd(left, right);
1422    case Xor: return opXor(left, right);
1423    case Implies: return opImplies(left, right);
1424    case Plus: return opPlus(left, right);
1425    case Times: return opTimes(left, right);
1426    case Minus: return opMinus(left, right);
1427    case Concatenate: return opConcatenate(left, right);
1428    case DivideBy: return opDivideBy(left, right);
1429    case Div: return opDiv(left, right);
1430    case Mod: return opMod(left, right);
1431    case Is: return opIs(left, right);
1432    case As: return opAs(left, right);
1433    default: 
1434      throw new Error("Not Done Yet: "+operation.toCode());
1435    }
1436  }
1437
1438  private List<Base> opAs(List<Base> left, List<Base> right) {
1439    List<Base> result = new ArrayList<>();
1440    if (right.size() != 1)
1441      return result;
1442    else {
1443      String tn = convertToString(right);
1444      for (Base nextLeft : left) {
1445        if (tn.equals(nextLeft.fhirType()))
1446          result.add(nextLeft);
1447      }
1448    }
1449    return result;
1450  }
1451
1452
1453  private List<Base> opIs(List<Base> left, List<Base> right) {
1454    List<Base> result = new ArrayList<Base>();
1455    if (left.size() != 1 || right.size() != 1) 
1456      result.add(new BooleanType(false).noExtensions());
1457    else {
1458      String tn = convertToString(right);
1459      if (left.get(0) instanceof org.hl7.fhir.r4.elementmodel.Element)
1460        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1461      else if ((left.get(0) instanceof Element) && ((Element) left.get(0)).isDisallowExtensions())
1462        result.add(new BooleanType(Utilities.capitalize(left.get(0).fhirType()).equals(tn) || ("System."+Utilities.capitalize(left.get(0).fhirType())).equals(tn)).noExtensions());
1463      else
1464        result.add(new BooleanType(left.get(0).hasType(tn)).noExtensions());
1465    }
1466    return result;
1467  }
1468
1469
1470  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) {
1471    switch (operation) {
1472    case Equals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1473    case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1474    case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1475    case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1476    case LessThan: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1477    case Greater: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1478    case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1479    case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1480    case Is: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1481    case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
1482    case Union: return left.union(right);
1483    case Or: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1484    case And: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1485    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1486    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1487    case Times: 
1488      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
1489      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1490        result.addType(TypeDetails.FP_Integer);
1491      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1492        result.addType(TypeDetails.FP_Decimal);
1493      return result;
1494    case DivideBy: 
1495      result = new TypeDetails(CollectionStatus.SINGLETON);
1496      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1497        result.addType(TypeDetails.FP_Decimal);
1498      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1499        result.addType(TypeDetails.FP_Decimal);
1500      return result;
1501    case Concatenate:
1502      result = new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
1503      return result;
1504    case Plus:
1505      result = new TypeDetails(CollectionStatus.SINGLETON);
1506      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1507        result.addType(TypeDetails.FP_Integer);
1508      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1509        result.addType(TypeDetails.FP_Decimal);
1510      else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri"))
1511        result.addType(TypeDetails.FP_String);
1512      return result;
1513    case Minus:
1514      result = new TypeDetails(CollectionStatus.SINGLETON);
1515      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1516        result.addType(TypeDetails.FP_Integer);
1517      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1518        result.addType(TypeDetails.FP_Decimal);
1519      return result;
1520    case Div: 
1521    case Mod: 
1522      result = new TypeDetails(CollectionStatus.SINGLETON);
1523      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1524        result.addType(TypeDetails.FP_Integer);
1525      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1526        result.addType(TypeDetails.FP_Decimal);
1527      return result;
1528    case In: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1529    case MemberOf: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1530    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
1531    default: 
1532      return null;
1533    }
1534  }
1535
1536
1537  private List<Base> opEquals(List<Base> left, List<Base> right) {
1538    if (left.size() == 0 || right.size() == 0) 
1539      return new ArrayList<Base>();
1540
1541    if (left.size() != right.size())
1542      return makeBoolean(false);
1543
1544    boolean res = true;
1545    boolean nil = false;
1546    for (int i = 0; i < left.size(); i++) {
1547      Boolean eq = doEquals(left.get(i), right.get(i));
1548      if (eq == null)
1549        nil = true;
1550      else if (eq == false) { 
1551        res = false;
1552        break;
1553      }
1554    }
1555    if (!res)
1556      return makeBoolean(res);
1557    else if (nil)
1558      return new ArrayList<Base>();
1559    else
1560      return makeBoolean(res);
1561  }
1562
1563  private List<Base> opNotEquals(List<Base> left, List<Base> right) {
1564    if (!legacyMode && (left.size() == 0 || right.size() == 0))
1565      return new ArrayList<Base>();
1566
1567    if (left.size() != right.size())
1568      return makeBoolean(true);
1569
1570    boolean res = true;
1571    boolean nil = false;
1572    for (int i = 0; i < left.size(); i++) {
1573      Boolean eq = doEquals(left.get(i), right.get(i));
1574      if (eq == null)
1575        nil = true;
1576      else if (eq == true) { 
1577        res = false;
1578        break;
1579      }
1580    }
1581    if (!res)
1582      return makeBoolean(res);
1583    else if (nil)
1584      return new ArrayList<Base>();
1585    else
1586      return makeBoolean(res);
1587  }
1588
1589  private String removeTrailingZeros(String s) {
1590    if (Utilities.noString(s))
1591      return "";
1592    int i = s.length()-1;
1593    boolean done = false;
1594    boolean dot = false;
1595    while (i > 0 && !done) {
1596      if (s.charAt(i) == '.') {
1597        i--;
1598        dot = true;
1599      }
1600      else if (!dot && s.charAt(i) == '0')
1601        i--;
1602      else
1603        done = true;
1604    }
1605    return s.substring(0, i+1);
1606  }
1607
1608  private boolean decEqual(String left, String right) {
1609    left = removeTrailingZeros(left);
1610    right = removeTrailingZeros(right);
1611    return left.equals(right);
1612  }
1613  
1614  private Boolean compareDates(BaseDateTimeType left, BaseDateTimeType right) {
1615    return left.equalsUsingFhirPathRules(right);
1616  }
1617  
1618  private Boolean doEquals(Base left, Base right) {
1619    if (left instanceof Quantity && right instanceof Quantity)
1620      return qtyEqual((Quantity) left, (Quantity) right);
1621    else if (left.isDateTime() && right.isDateTime()) {
1622      return compareDates(left.dateTimeValue(), right.dateTimeValue());
1623    } else if (left instanceof DecimalType || right instanceof DecimalType) 
1624      return decEqual(left.primitiveValue(), right.primitiveValue());
1625    else if (left.isPrimitive() && right.isPrimitive())
1626                        return Base.equals(left.primitiveValue(), right.primitiveValue());
1627    else
1628      return Base.compareDeep(left, right, false);
1629  }
1630
1631
1632  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
1633    if (left instanceof Quantity && right instanceof Quantity)
1634      return qtyEquivalent((Quantity) left, (Quantity) right);
1635    if (left.hasType("integer") && right.hasType("integer"))
1636      return doEquals(left, right);
1637    if (left.hasType("boolean") && right.hasType("boolean"))
1638      return doEquals(left, right);
1639    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt"))
1640      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1641    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant"))
1642      return compareDateTimeElements(left, right, true) == 0;
1643    if (left.hasType(FHIR_TYPES_STRING) && right.hasType(FHIR_TYPES_STRING))
1644      return Utilities.equivalent(convertToString(left), convertToString(right));
1645
1646    throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()));
1647  }
1648
1649  private boolean qtyEqual(Quantity left, Quantity right) {
1650    if (worker.getUcumService() != null) {
1651      DecimalType dl = qtyToCanonical(left);
1652      DecimalType dr = qtyToCanonical(right);
1653      if (dl != null && dr != null) 
1654        return doEquals(dl,  dr);
1655    }
1656    return left.equals(right);
1657  }
1658
1659  private DecimalType qtyToCanonical(Quantity q) {
1660    if (!"http://unitsofmeasure.org".equals(q.getSystem()))
1661      return null;
1662    try {
1663      Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
1664      Pair c = worker.getUcumService().getCanonicalForm(p);
1665      return new DecimalType(c.getValue().asDecimal());
1666    } catch (UcumException e) {
1667      return null;
1668    }
1669  }
1670
1671  private Base pairToQty(Pair p) {
1672    return new Quantity().setValue(new BigDecimal(p.getValue().toString())).setSystem("http://unitsofmeasure.org").setCode(p.getCode()).noExtensions();
1673  }
1674
1675
1676  private Pair qtyToPair(Quantity q) {
1677    if (!"http://unitsofmeasure.org".equals(q.getSystem()))
1678      return null;
1679    try {
1680      return new Pair(new Decimal(q.getValue().toPlainString()), q.getCode());
1681    } catch (UcumException e) {
1682      return null;
1683    }
1684  }
1685
1686
1687  private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
1688    if (worker.getUcumService() != null) {
1689      DecimalType dl = qtyToCanonical(left);
1690      DecimalType dr = qtyToCanonical(right);
1691      if (dl != null && dr != null) 
1692        return doEquivalent(dl,  dr);
1693    }
1694    return left.equals(right);
1695  }
1696
1697
1698
1699  private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1700    if (left.size() != right.size())
1701      return makeBoolean(false);
1702
1703    boolean res = true;
1704    for (int i = 0; i < left.size(); i++) {
1705      boolean found = false;
1706      for (int j = 0; j < right.size(); j++) {
1707        if (doEquivalent(left.get(i), right.get(j))) {
1708          found = true;
1709          break;
1710        }
1711      }
1712      if (!found) {
1713        res = false;
1714        break;
1715      }
1716    }
1717    return makeBoolean(res);
1718  }
1719
1720  private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1721    if (left.size() != right.size())
1722      return makeBoolean(true);
1723
1724    boolean res = true;
1725    for (int i = 0; i < left.size(); i++) {
1726      boolean found = false;
1727      for (int j = 0; j < right.size(); j++) {
1728        if (doEquivalent(left.get(i), right.get(j))) {
1729          found = true;
1730          break;
1731        }
1732      }
1733      if (!found) {
1734        res = false;
1735        break;
1736      }
1737    }
1738    return makeBoolean(!res);
1739  }
1740
1741  private final static String[] FHIR_TYPES_STRING = new String[] {"string", "uri", "code", "oid", "id", "uuid", "sid", "markdown", "base64Binary", "canonical", "url"};
1742
1743        private List<Base> opLessThan(List<Base> left, List<Base> right) throws FHIRException {
1744    if (left.size() == 0 || right.size() == 0) 
1745      return new ArrayList<Base>();
1746    
1747    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1748      Base l = left.get(0);
1749      Base r = right.get(0);
1750      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 
1751        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1752      else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal"))) 
1753        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
1754      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1755        return makeBoolean(compareDateTimeElements(l, r, false) < 0);
1756      else if ((l.hasType("time")) && (r.hasType("time"))) 
1757        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1758      else
1759        throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType());
1760    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1761      List<Base> lUnit = left.get(0).listChildrenByName("code");
1762      List<Base> rUnit = right.get(0).listChildrenByName("code");
1763      if (Base.compareDeep(lUnit, rUnit, true)) {
1764        return opLessThan(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1765      } else {
1766        if (worker.getUcumService() == null)
1767          return makeBoolean(false);
1768        else {
1769          List<Base> dl = new ArrayList<Base>();
1770          dl.add(qtyToCanonical((Quantity) left.get(0)));
1771          List<Base> dr = new ArrayList<Base>();
1772          dr.add(qtyToCanonical((Quantity) right.get(0)));
1773          return opLessThan(dl, dr);
1774        }
1775      }
1776    }
1777    return new ArrayList<Base>();
1778  }
1779
1780        private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException {
1781    if (left.size() == 0 || right.size() == 0) 
1782      return new ArrayList<Base>();
1783    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1784      Base l = left.get(0);
1785      Base r = right.get(0);
1786      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 
1787        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1788      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1789        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
1790      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1791        return makeBoolean(compareDateTimeElements(l, r, false) > 0);
1792      else if ((l.hasType("time")) && (r.hasType("time"))) 
1793        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1794      else
1795        throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType());
1796    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1797      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1798      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1799      if (Base.compareDeep(lUnit, rUnit, true)) {
1800        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1801      } else {
1802        if (worker.getUcumService() == null)
1803          return makeBoolean(false);
1804        else {
1805          List<Base> dl = new ArrayList<Base>();
1806          dl.add(qtyToCanonical((Quantity) left.get(0)));
1807          List<Base> dr = new ArrayList<Base>();
1808          dr.add(qtyToCanonical((Quantity) right.get(0)));
1809          return opGreater(dl, dr);
1810        }
1811      }
1812    }
1813    return new ArrayList<Base>();
1814  }
1815
1816        private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1817    if (left.size() == 0 || right.size() == 0) 
1818      return new ArrayList<Base>();
1819    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1820      Base l = left.get(0);
1821      Base r = right.get(0);
1822      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 
1823        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1824      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1825        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
1826      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1827        return makeBoolean(compareDateTimeElements(l, r, false) <= 0);
1828      else if ((l.hasType("time")) && (r.hasType("time"))) 
1829        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1830      else
1831        throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType());
1832    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1833      List<Base> lUnits = left.get(0).listChildrenByName("unit");
1834      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
1835      List<Base> rUnits = right.get(0).listChildrenByName("unit");
1836      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
1837      if ((lunit == null && runit == null) || lunit.equals(runit)) {
1838        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1839      } else {
1840        if (worker.getUcumService() == null)
1841          return makeBoolean(false);
1842        else {
1843          List<Base> dl = new ArrayList<Base>();
1844          dl.add(qtyToCanonical((Quantity) left.get(0)));
1845          List<Base> dr = new ArrayList<Base>();
1846          dr.add(qtyToCanonical((Quantity) right.get(0)));
1847          return opLessOrEqual(dl, dr);
1848        }
1849      }
1850    }
1851    return new ArrayList<Base>();
1852  }
1853
1854        private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1855    if (left.size() == 0 || right.size() == 0) 
1856      return new ArrayList<Base>();
1857    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1858      Base l = left.get(0);
1859      Base r = right.get(0);
1860      if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 
1861        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1862      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt"))) 
1863        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
1864      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1865        return makeBoolean(compareDateTimeElements(l, r, false) >= 0);
1866      else if ((l.hasType("time")) && (r.hasType("time"))) 
1867        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1868      else
1869        throw new PathEngineException("Unable to compare values of type "+l.fhirType()+" and "+r.fhirType());
1870    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1871      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1872      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1873      if (Base.compareDeep(lUnit, rUnit, true)) {
1874        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1875      } else {
1876        if (worker.getUcumService() == null)
1877          return makeBoolean(false);
1878        else {
1879          List<Base> dl = new ArrayList<Base>();
1880          dl.add(qtyToCanonical((Quantity) left.get(0)));
1881          List<Base> dr = new ArrayList<Base>();
1882          dr.add(qtyToCanonical((Quantity) right.get(0)));
1883          return opGreaterOrEqual(dl, dr);
1884        }
1885      }
1886    }
1887    return new ArrayList<Base>();
1888  }
1889
1890        private List<Base> opMemberOf(List<Base> left, List<Base> right) throws FHIRException {
1891          boolean ans = false;
1892          ValueSet vs = worker.fetchResource(ValueSet.class, right.get(0).primitiveValue());
1893          if (vs != null) {
1894            for (Base l : left) {
1895              if (l.fhirType().equals("code")) {
1896          if (worker.validateCode(l.castToCoding(l), vs).isOk())
1897            ans = true;
1898              } else if (l.fhirType().equals("Coding")) {
1899                if (worker.validateCode(l.castToCoding(l), vs).isOk())
1900                  ans = true;
1901              } else if (l.fhirType().equals("CodeableConcept")) {
1902                if (worker.validateCode(l.castToCodeableConcept(l), vs).isOk())
1903                  ans = true;
1904              }
1905            }
1906          }
1907          return makeBoolean(ans);
1908        }
1909
1910  private List<Base> opIn(List<Base> left, List<Base> right) throws FHIRException {
1911    if (left.size() == 0) 
1912      return new ArrayList<Base>();
1913    if (right.size() == 0) 
1914      return makeBoolean(false);
1915    boolean ans = true;
1916    for (Base l : left) {
1917      boolean f = false;
1918      for (Base r : right) {
1919        Boolean eq = doEquals(l, r);
1920        if (eq != null && eq == true) {
1921          f = true;
1922          break;
1923        }
1924      }
1925      if (!f) {
1926        ans = false;
1927        break;
1928      }
1929    }
1930    return makeBoolean(ans);
1931  }
1932
1933  private List<Base> opContains(List<Base> left, List<Base> right) {
1934    if (left.size() == 0 || right.size() == 0) 
1935     return new ArrayList<Base>();
1936    boolean ans = true;
1937    for (Base r : right) {
1938      boolean f = false;
1939      for (Base l : left) {
1940        Boolean eq = doEquals(l, r);
1941        if (eq != null && eq == true) {
1942          f = true;
1943          break;
1944        }
1945      }
1946      if (!f) {
1947        ans = false;
1948        break;
1949      }
1950    }
1951    return makeBoolean(ans);
1952  }
1953
1954  private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException {
1955    if (left.size() == 0 || right.size() == 0) 
1956      return new ArrayList<Base>();
1957    if (left.size() > 1)
1958      throw new PathEngineException("Error performing +: left operand has more than one value");
1959    if (!left.get(0).isPrimitive())
1960      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1961    if (right.size() > 1)
1962      throw new PathEngineException("Error performing +: right operand has more than one value");
1963    if (!right.get(0).isPrimitive())
1964      throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType()));
1965
1966    List<Base> result = new ArrayList<Base>();
1967    Base l = left.get(0);
1968    Base r = right.get(0);
1969    if (l.hasType(FHIR_TYPES_STRING) && r.hasType(FHIR_TYPES_STRING)) 
1970      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
1971    else if (l.hasType("integer") && r.hasType("integer")) 
1972      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
1973    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
1974      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
1975    else
1976      throw new PathEngineException(String.format("Error performing +: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
1977    return result;
1978  }
1979
1980  private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException {
1981    if (left.size() == 0 || right.size() == 0) 
1982      return new ArrayList<Base>();
1983    if (left.size() > 1)
1984      throw new PathEngineException("Error performing *: left operand has more than one value");
1985    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
1986      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1987    if (right.size() > 1)
1988      throw new PathEngineException("Error performing *: right operand has more than one value");
1989    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
1990      throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType()));
1991
1992    List<Base> result = new ArrayList<Base>();
1993    Base l = left.get(0);
1994    Base r = right.get(0);
1995
1996    if (l.hasType("integer") && r.hasType("integer")) 
1997      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
1998    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
1999      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
2000    else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2001      Pair pl = qtyToPair((Quantity) l);
2002      Pair pr = qtyToPair((Quantity) r);
2003      Pair p;
2004      try {
2005        p = worker.getUcumService().multiply(pl, pr);
2006        result.add(pairToQty(p));
2007      } catch (UcumException e) {
2008        throw new PathEngineException(e.getMessage(), e);
2009      }
2010    } else
2011      throw new PathEngineException(String.format("Error performing *: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
2012    return result;
2013  }
2014
2015
2016  private List<Base> opConcatenate(List<Base> left, List<Base> right) throws PathEngineException {
2017    if (left.size() > 1)
2018      throw new PathEngineException("Error performing &: left operand has more than one value");
2019    if (left.size() > 0 && !left.get(0).hasType(FHIR_TYPES_STRING))
2020      throw new PathEngineException(String.format("Error performing &: left operand has the wrong type (%s)", left.get(0).fhirType()));
2021    if (right.size() > 1)
2022      throw new PathEngineException("Error performing &: right operand has more than one value");
2023    if (right.size() > 0 && !right.get(0).hasType(FHIR_TYPES_STRING))
2024      throw new PathEngineException(String.format("Error performing &: right operand has the wrong type (%s)", right.get(0).fhirType()));
2025
2026    List<Base> result = new ArrayList<Base>();
2027    String l = left.size() == 0 ? "" : left.get(0).primitiveValue();
2028    String r = right.size() == 0 ? "" : right.get(0).primitiveValue();
2029    result.add(new StringType(l + r));
2030    return result;
2031  }
2032
2033  private List<Base> opUnion(List<Base> left, List<Base> right) {
2034    List<Base> result = new ArrayList<Base>();
2035    for (Base item : left) {
2036      if (!doContains(result, item))
2037        result.add(item);
2038    }
2039    for (Base item : right) {
2040      if (!doContains(result, item))
2041        result.add(item);
2042    }
2043    return result;
2044  }
2045
2046  private boolean doContains(List<Base> list, Base item) {
2047    for (Base test : list) {
2048      Boolean eq = doEquals(test, item);
2049      if (eq != null && eq == true)
2050        return true;
2051    }
2052    return false;
2053  }
2054
2055
2056  private List<Base> opAnd(List<Base> left, List<Base> right) throws PathEngineException {
2057    Equality l = asBool(left);
2058    Equality r = asBool(right);
2059    switch (l) {
2060    case False: return makeBoolean(false);
2061    case Null:
2062      if (r == Equality.False)
2063        return makeBoolean(false);
2064      else
2065        return makeNull();
2066    case True:
2067      switch (r) {
2068      case False: return makeBoolean(false);
2069      case Null: return makeNull();
2070      case True: return makeBoolean(true);
2071      }
2072    }
2073    return makeNull();
2074  }
2075
2076  private boolean isBoolean(List<Base> list, boolean b) {
2077    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
2078  }
2079
2080  private List<Base> opOr(List<Base> left, List<Base> right) throws PathEngineException {
2081    Equality l = asBool(left);
2082    Equality r = asBool(right);
2083    switch (l) {
2084    case True: return makeBoolean(true);
2085    case Null:
2086      if (r == Equality.True)
2087        return makeBoolean(true);
2088      else
2089        return makeNull();
2090    case False:
2091      switch (r) {
2092      case False: return makeBoolean(false);
2093      case Null: return makeNull();
2094      case True: return makeBoolean(true);
2095      }
2096    }
2097    return makeNull();
2098  }
2099
2100  private List<Base> opXor(List<Base> left, List<Base> right) throws PathEngineException {
2101    Equality l = asBool(left);
2102    Equality r = asBool(right);
2103    switch (l) {
2104    case True: 
2105      switch (r) {
2106      case False: return makeBoolean(true);
2107      case True: return makeBoolean(false);
2108      case Null: return makeNull();
2109      }
2110    case Null:
2111      return makeNull();
2112    case False:
2113      switch (r) {
2114      case False: return makeBoolean(false);
2115      case True: return makeBoolean(true);
2116      case Null: return makeNull();
2117      }
2118    }
2119    return makeNull();
2120  }
2121
2122  private List<Base> opImplies(List<Base> left, List<Base> right) throws PathEngineException {
2123    Equality eq = asBool(left);
2124    if (eq == Equality.False) 
2125      return makeBoolean(true);
2126    else if (right.size() == 0)
2127      return makeNull();
2128    else switch (asBool(right)) {
2129    case False: return eq == Equality.Null ? makeNull() : makeBoolean(false);
2130    case Null: return makeNull();
2131    case True: return makeBoolean(true);
2132    }
2133    return makeNull();
2134  }
2135
2136
2137  private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException {
2138    if (left.size() == 0 || right.size() == 0) 
2139      return new ArrayList<Base>();
2140    if (left.size() > 1)
2141      throw new PathEngineException("Error performing -: left operand has more than one value");
2142    if (!left.get(0).isPrimitive())
2143      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
2144    if (right.size() > 1)
2145      throw new PathEngineException("Error performing -: right operand has more than one value");
2146    if (!right.get(0).isPrimitive())
2147      throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType()));
2148
2149    List<Base> result = new ArrayList<Base>();
2150    Base l = left.get(0);
2151    Base r = right.get(0);
2152
2153    if (l.hasType("integer") && r.hasType("integer")) 
2154      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
2155    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) 
2156      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
2157    else
2158      throw new PathEngineException(String.format("Error performing -: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
2159    return result;
2160  }
2161
2162  private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException {
2163    if (left.size() == 0 || right.size() == 0) 
2164      return new ArrayList<Base>();
2165    if (left.size() > 1)
2166      throw new PathEngineException("Error performing /: left operand has more than one value");
2167    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
2168      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
2169    if (right.size() > 1)
2170      throw new PathEngineException("Error performing /: right operand has more than one value");
2171    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
2172      throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType()));
2173
2174    List<Base> result = new ArrayList<Base>();
2175    Base l = left.get(0);
2176    Base r = right.get(0);
2177
2178    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
2179      Decimal d1;
2180      try {
2181        d1 = new Decimal(l.primitiveValue());
2182        Decimal d2 = new Decimal(r.primitiveValue());
2183        result.add(new DecimalType(d1.divide(d2).asDecimal()));
2184      } catch (UcumException e) {
2185        throw new PathEngineException(e);
2186      }
2187    } else if (l instanceof Quantity && r instanceof Quantity && worker.getUcumService() != null) {
2188      Pair pl = qtyToPair((Quantity) l);
2189      Pair pr = qtyToPair((Quantity) r);
2190      Pair p;
2191      try {
2192        p = worker.getUcumService().multiply(pl, pr);
2193        result.add(pairToQty(p));
2194      } catch (UcumException e) {
2195        throw new PathEngineException(e.getMessage(), e);
2196      }
2197    } else
2198      throw new PathEngineException(String.format("Error performing /: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
2199    return result;
2200  }
2201
2202  private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException {
2203    if (left.size() == 0 || right.size() == 0) 
2204      return new ArrayList<Base>();
2205    if (left.size() > 1)
2206      throw new PathEngineException("Error performing div: left operand has more than one value");
2207    if (!left.get(0).isPrimitive() && !(left.get(0) instanceof Quantity))
2208      throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType()));
2209    if (right.size() > 1)
2210      throw new PathEngineException("Error performing div: right operand has more than one value");
2211    if (!right.get(0).isPrimitive() && !(right.get(0) instanceof Quantity))
2212      throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType()));
2213
2214    List<Base> result = new ArrayList<Base>();
2215    Base l = left.get(0);
2216    Base r = right.get(0);
2217
2218    if (l.hasType("integer") && r.hasType("integer")) 
2219      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue())));
2220    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) { 
2221      Decimal d1;
2222      try {
2223        d1 = new Decimal(l.primitiveValue());
2224        Decimal d2 = new Decimal(r.primitiveValue());
2225        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
2226      } catch (UcumException e) {
2227        throw new PathEngineException(e);
2228      }
2229    }
2230    else
2231      throw new PathEngineException(String.format("Error performing div: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
2232    return result;
2233  }
2234
2235  private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException {
2236    if (left.size() == 0 || right.size() == 0) 
2237      return new ArrayList<Base>();
2238    if (left.size() > 1)
2239      throw new PathEngineException("Error performing mod: left operand has more than one value");
2240    if (!left.get(0).isPrimitive())
2241      throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType()));
2242    if (right.size() > 1)
2243      throw new PathEngineException("Error performing mod: right operand has more than one value");
2244    if (!right.get(0).isPrimitive())
2245      throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType()));
2246
2247    List<Base> result = new ArrayList<Base>();
2248    Base l = left.get(0);
2249    Base r = right.get(0);
2250
2251    if (l.hasType("integer") && r.hasType("integer")) 
2252      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue())));
2253    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
2254      Decimal d1;
2255      try {
2256        d1 = new Decimal(l.primitiveValue());
2257        Decimal d2 = new Decimal(r.primitiveValue());
2258        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
2259      } catch (UcumException e) {
2260        throw new PathEngineException(e);
2261      }
2262    }
2263    else
2264      throw new PathEngineException(String.format("Error performing mod: left and right operand have incompatible or illegal types (%s, %s)", left.get(0).fhirType(), right.get(0).fhirType()));
2265    return result;
2266  }
2267
2268
2269  private TypeDetails resolveConstantType(ExecutionTypeContext context, Base constant) throws PathEngineException {
2270    if (constant instanceof BooleanType) 
2271      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2272    else if (constant instanceof IntegerType)
2273      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2274    else if (constant instanceof DecimalType)
2275      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
2276    else if (constant instanceof Quantity)
2277      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
2278    else if (constant instanceof FHIRConstant)
2279      return resolveConstantType(context, ((FHIRConstant) constant).getValue());
2280    else
2281      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2282  }
2283
2284  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException {
2285    if (s.startsWith("@")) {
2286      if (s.startsWith("@T"))
2287        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
2288      else
2289        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2290    } else if (s.equals("%sct"))
2291      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2292    else if (s.equals("%loinc"))
2293      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2294    else if (s.equals("%ucum"))
2295      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2296    else if (s.equals("%resource")) {
2297      if (context.resource == null)
2298        throw new PathEngineException("%resource cannot be used in this context");
2299      return new TypeDetails(CollectionStatus.SINGLETON, context.resource);
2300    } else if (s.equals("%context")) {
2301      return context.context;
2302    } else if (s.equals("%map-codes"))
2303      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2304    else if (s.equals("%us-zip"))
2305      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2306    else if (s.startsWith("%`vs-"))
2307      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2308    else if (s.startsWith("%`cs-"))
2309      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2310    else if (s.startsWith("%`ext-"))
2311      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2312    else if (hostServices == null)
2313      throw new PathEngineException("Unknown fixed constant type for '"+s+"'");
2314    else
2315      return hostServices.resolveConstantType(context.appInfo, s);
2316  }
2317
2318        private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
2319    List<Base> result = new ArrayList<Base>(); 
2320    if (atEntry && context.appInfo != null && hostServices != null) {
2321      // we'll see if the name matches a constant known by the context.
2322      Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true);
2323      if (temp != null) {
2324        result.add(temp);
2325        return result;
2326      }
2327    }
2328    if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
2329      if (item.isResource() && item.fhirType().equals(exp.getName()))  
2330        result.add(item);
2331    } else 
2332      getChildrenByName(item, exp.getName(), result);
2333    if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) {
2334      // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
2335      // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
2336      Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false);
2337      if (temp != null) {
2338        result.add(temp);
2339      }
2340    }
2341    return result;
2342  }     
2343
2344  private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException {
2345    if (hostServices == null)
2346      throw new PathEngineException("Unable to resolve context reference since no host services are provided");
2347    return hostServices.resolveConstantType(context.appInfo, name);
2348  }
2349  
2350  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
2351    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up
2352      return new TypeDetails(CollectionStatus.SINGLETON, type);
2353    TypeDetails result = new TypeDetails(null);
2354    getChildTypesByName(type, exp.getName(), result);
2355    return result;
2356  }
2357
2358
2359  private String hashTail(String type) {
2360    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
2361  }
2362
2363
2364  @SuppressWarnings("unchecked")
2365  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
2366    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
2367    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType)
2368      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
2369    else
2370      for (ExpressionNode expr : exp.getParameters()) {
2371        if (exp.getFunction() == Function.Where || exp.getFunction() == Function.All || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat || exp.getFunction() == Function.Aggregate)
2372          paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
2373        else
2374          paramTypes.add(executeType(context, focus, expr, true));
2375      }
2376    switch (exp.getFunction()) {
2377    case Empty : 
2378      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2379    case Not : 
2380      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2381    case Exists : 
2382      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2383    case SubsetOf : {
2384      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 
2385      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2386    }
2387    case SupersetOf : {
2388      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus); 
2389      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2390    }
2391    case IsDistinct : 
2392      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2393    case Distinct : 
2394      return focus;
2395    case Count : 
2396      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2397    case Where : 
2398      return focus;
2399    case Select : 
2400      return anything(focus.getCollectionStatus());
2401    case All : 
2402      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2403    case Repeat : 
2404      return anything(focus.getCollectionStatus());
2405    case Aggregate : 
2406      return anything(focus.getCollectionStatus());
2407    case Item : {
2408      checkOrdered(focus, "item");
2409      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2410      return focus; 
2411    }
2412    case As : {
2413      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2414      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
2415    }
2416    case OfType : { 
2417      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2418      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
2419    }
2420    case Type : { 
2421      boolean s = false;
2422      boolean c = false;
2423      for (ProfiledType pt : focus.getProfiledTypes()) {
2424        s = s || pt.isSystemType();
2425        c = c || !pt.isSystemType();
2426      }
2427      if (s && c)
2428        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo, TypeDetails.FP_ClassInfo);
2429      else if (s)
2430        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_SimpleTypeInfo);
2431      else
2432        return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_ClassInfo);
2433    }
2434    case Is : {
2435      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2436      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2437    }
2438    case Single :
2439      return focus.toSingleton();
2440    case First : {
2441      checkOrdered(focus, "first");
2442      return focus.toSingleton();
2443    }
2444    case Last : {
2445      checkOrdered(focus, "last");
2446      return focus.toSingleton();
2447    }
2448    case Tail : {
2449      checkOrdered(focus, "tail");
2450      return focus;
2451    }
2452    case Skip : {
2453      checkOrdered(focus, "skip");
2454      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2455      return focus;
2456    }
2457    case Take : {
2458      checkOrdered(focus, "take");
2459      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2460      return focus;
2461    }
2462    case Union : {
2463      return focus.union(paramTypes.get(0));
2464    }
2465    case Combine : {
2466      return focus.union(paramTypes.get(0));
2467    }
2468    case Intersect : {
2469      return focus.intersect(paramTypes.get(0));
2470    }
2471    case Exclude : {
2472      return focus;
2473    }
2474    case Iif : {
2475      TypeDetails types = new TypeDetails(null);
2476      types.update(paramTypes.get(0));
2477      if (paramTypes.size() > 1)
2478        types.update(paramTypes.get(1));
2479      return types;
2480    }
2481    case Lower : {
2482      checkContextString(focus, "lower");
2483      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2484    }
2485    case Upper : {
2486      checkContextString(focus, "upper");
2487      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2488    }
2489    case ToChars : {
2490      checkContextString(focus, "toChars");
2491      return new TypeDetails(CollectionStatus.ORDERED, TypeDetails.FP_String); 
2492    }
2493    case Substring : {
2494      checkContextString(focus, "subString");
2495      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer)); 
2496      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2497    }
2498    case StartsWith : {
2499      checkContextString(focus, "startsWith");
2500      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2501      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2502    }
2503    case EndsWith : {
2504      checkContextString(focus, "endsWith");
2505      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2506      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2507    }
2508    case Matches : {
2509      checkContextString(focus, "matches");
2510      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2511      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean); 
2512    }
2513    case ReplaceMatches : {
2514      checkContextString(focus, "replaceMatches");
2515      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2516      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String); 
2517    }
2518    case Contains : {
2519      checkContextString(focus, "contains");
2520      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2521      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2522    }
2523    case Replace : {
2524      checkContextString(focus, "replace");
2525      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2526      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2527    }
2528    case Length : { 
2529      checkContextPrimitive(focus, "length", false);
2530      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2531    }
2532    case Children : 
2533      return childTypes(focus, "*");
2534    case Descendants : 
2535      return childTypes(focus, "**");
2536    case MemberOf : {
2537      checkContextCoded(focus, "memberOf");
2538      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2539      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2540    }
2541    case Trace : {
2542      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2543      return focus; 
2544    }
2545    case Today : 
2546      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2547    case Now : 
2548      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2549    case Resolve : {
2550      checkContextReference(focus, "resolve");
2551      return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource"); 
2552    }
2553    case Extension : {
2554      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2555      return new TypeDetails(CollectionStatus.SINGLETON, "Extension"); 
2556    }
2557    case AnyTrue: 
2558      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2559    case AllTrue: 
2560      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2561    case AnyFalse: 
2562      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2563    case AllFalse: 
2564      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2565    case HasValue : 
2566      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2567    case HtmlChecks : 
2568      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2569    case Alias : 
2570      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2571      return anything(CollectionStatus.SINGLETON); 
2572    case AliasAs : 
2573      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2574      return focus; 
2575    case ToInteger : {
2576      checkContextPrimitive(focus, "toInteger", true);
2577      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
2578    }
2579    case ToDecimal : {
2580      checkContextPrimitive(focus, "toDecimal", true);
2581      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
2582    }
2583    case ToString : {
2584      checkContextPrimitive(focus, "toString", true);
2585      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String);
2586    }
2587    case ToQuantity : {
2588      checkContextPrimitive(focus, "toQuantity", true);
2589      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Quantity);
2590    }
2591    case ToBoolean : {
2592      checkContextPrimitive(focus, "toBoolean", false);
2593      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2594    }
2595    case ToDateTime : {
2596      checkContextPrimitive(focus, "toBoolean", false);
2597      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
2598    }
2599    case ToTime : {
2600      checkContextPrimitive(focus, "toBoolean", false);
2601      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time);
2602    }
2603    case ConvertsToString : 
2604    case ConvertsToQuantity :{
2605      checkContextPrimitive(focus, exp.getFunction().toCode(), true);
2606      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2607    } 
2608    case ConvertsToInteger : 
2609    case ConvertsToDecimal : 
2610    case ConvertsToDateTime : 
2611    case ConvertsToTime : 
2612    case ConvertsToBoolean : {
2613      checkContextPrimitive(focus, exp.getFunction().toCode(), false);
2614      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
2615    }
2616    case ConformsTo: {
2617      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 
2618      return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);       
2619    }
2620    case Custom : {
2621      return hostServices.checkFunction(context.appInfo, exp.getName(), paramTypes);
2622    }
2623    default:
2624      break;
2625    }
2626    throw new Error("not Implemented yet");
2627  }
2628
2629
2630  private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
2631    int i = 0;
2632    for (TypeDetails pt : typeSet) {
2633      if (i == paramTypes.size())
2634        return;
2635      TypeDetails actual = paramTypes.get(i);
2636      i++;
2637      for (String a : actual.getTypes()) {
2638        if (!pt.hasType(worker, a))
2639          throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString()); 
2640      }
2641    }
2642  }
2643
2644  private void checkOrdered(TypeDetails focus, String name) throws PathEngineException {
2645    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED)
2646      throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections"); 
2647  }
2648
2649  private void checkContextReference(TypeDetails focus, String name) throws PathEngineException {
2650    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference") && !focus.hasType(worker, "canonical"))
2651      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, canonical, Reference"); 
2652  }
2653
2654
2655  private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException {
2656    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept"))
2657      throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept");     
2658  }
2659
2660
2661  private void checkContextString(TypeDetails focus, String name) throws PathEngineException {
2662    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id"))
2663      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe()); 
2664  }
2665
2666
2667  private void checkContextPrimitive(TypeDetails focus, String name, boolean canQty) throws PathEngineException {
2668    if (canQty) {
2669       if (!focus.hasType(primitiveTypes) && !focus.hasType("Quantity"))
2670        throw new PathEngineException("The function '"+name+"'() can only be used on a Quantity or on "+primitiveTypes.toString()); 
2671    } else if (!focus.hasType(primitiveTypes))
2672      throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString()); 
2673  }
2674
2675
2676  private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException {
2677    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
2678    for (String f : focus.getTypes()) 
2679      getChildTypesByName(f, mask, result);
2680    return result;
2681  }
2682
2683  private TypeDetails anything(CollectionStatus status) {
2684    return new TypeDetails(status, allTypes.keySet());
2685  }
2686
2687  //    private boolean isPrimitiveType(String s) {
2688  //            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");
2689  //    }
2690
2691        private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2692    switch (exp.getFunction()) {
2693    case Empty : return funcEmpty(context, focus, exp);
2694    case Not : return funcNot(context, focus, exp);
2695    case Exists : return funcExists(context, focus, exp);
2696    case SubsetOf : return funcSubsetOf(context, focus, exp);
2697    case SupersetOf : return funcSupersetOf(context, focus, exp);
2698    case IsDistinct : return funcIsDistinct(context, focus, exp);
2699    case Distinct : return funcDistinct(context, focus, exp);
2700    case Count : return funcCount(context, focus, exp);
2701    case Where : return funcWhere(context, focus, exp);
2702    case Select : return funcSelect(context, focus, exp);
2703    case All : return funcAll(context, focus, exp);
2704    case Repeat : return funcRepeat(context, focus, exp);
2705    case Aggregate : return funcAggregate(context, focus, exp);
2706    case Item : return funcItem(context, focus, exp);
2707    case As : return funcAs(context, focus, exp);
2708    case OfType : return funcAs(context, focus, exp);
2709    case Type : return funcType(context, focus, exp);
2710    case Is : return funcIs(context, focus, exp);
2711    case Single : return funcSingle(context, focus, exp);
2712    case First : return funcFirst(context, focus, exp);
2713    case Last : return funcLast(context, focus, exp);
2714    case Tail : return funcTail(context, focus, exp);
2715    case Skip : return funcSkip(context, focus, exp);
2716    case Take : return funcTake(context, focus, exp);
2717    case Union : return funcUnion(context, focus, exp);
2718    case Combine : return funcCombine(context, focus, exp);
2719    case Intersect : return funcIntersect(context, focus, exp);
2720    case Exclude : return funcExclude(context, focus, exp);
2721    case Iif : return funcIif(context, focus, exp);
2722    case Lower : return funcLower(context, focus, exp);
2723    case Upper : return funcUpper(context, focus, exp);
2724    case ToChars : return funcToChars(context, focus, exp);
2725    case Substring : return funcSubstring(context, focus, exp);
2726    case StartsWith : return funcStartsWith(context, focus, exp);
2727    case EndsWith : return funcEndsWith(context, focus, exp);
2728    case Matches : return funcMatches(context, focus, exp);
2729    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
2730    case Contains : return funcContains(context, focus, exp);
2731    case Replace : return funcReplace(context, focus, exp);
2732    case Length : return funcLength(context, focus, exp);
2733    case Children : return funcChildren(context, focus, exp);
2734    case Descendants : return funcDescendants(context, focus, exp);
2735    case MemberOf : return funcMemberOf(context, focus, exp);
2736    case Trace : return funcTrace(context, focus, exp);
2737    case Today : return funcToday(context, focus, exp);
2738    case Now : return funcNow(context, focus, exp);
2739    case Resolve : return funcResolve(context, focus, exp);
2740    case Extension : return funcExtension(context, focus, exp);
2741    case AnyFalse: return funcAnyFalse(context, focus, exp);
2742    case AllFalse: return funcAllFalse(context, focus, exp);
2743    case AnyTrue: return funcAnyTrue(context, focus, exp);
2744    case AllTrue: return funcAllTrue(context, focus, exp);
2745    case HasValue : return funcHasValue(context, focus, exp);
2746    case AliasAs : return funcAliasAs(context, focus, exp);
2747    case Alias : return funcAlias(context, focus, exp);
2748    case HtmlChecks : return funcHtmlChecks(context, focus, exp);
2749    case ToInteger : return funcToInteger(context, focus, exp);
2750    case ToDecimal : return funcToDecimal(context, focus, exp);
2751    case ToString : return funcToString(context, focus, exp);
2752    case ToBoolean : return funcToBoolean(context, focus, exp);
2753    case ToQuantity : return funcToQuantity(context, focus, exp);
2754    case ToDateTime : return funcToDateTime(context, focus, exp);
2755    case ToTime : return funcToTime(context, focus, exp);
2756    case ConvertsToInteger : return funcIsInteger(context, focus, exp);
2757    case ConvertsToDecimal : return funcIsDecimal(context, focus, exp);
2758    case ConvertsToString : return funcIsString(context, focus, exp);
2759    case ConvertsToBoolean : return funcIsBoolean(context, focus, exp);
2760    case ConvertsToQuantity : return funcIsQuantity(context, focus, exp);
2761    case ConvertsToDateTime : return funcIsDateTime(context, focus, exp);
2762    case ConvertsToTime : return funcIsTime(context, focus, exp);
2763    case ConformsTo : return funcConformsTo(context, focus, exp); 
2764    case Custom: { 
2765      List<List<Base>> params = new ArrayList<List<Base>>();
2766      for (ExpressionNode p : exp.getParameters()) 
2767        params.add(execute(context, focus, p, true));
2768      return hostServices.executeFunction(context.appInfo, exp.getName(), params);
2769    }
2770    default:
2771      throw new Error("not Implemented yet");
2772    }
2773  }
2774
2775        private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2776    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2777    String name = nl.get(0).primitiveValue();
2778    context.addAlias(name, focus);
2779    return focus;
2780  }
2781
2782  private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2783    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2784    String name = nl.get(0).primitiveValue();
2785    List<Base> res = new ArrayList<Base>();
2786    Base b = context.getAlias(name);
2787    if (b != null)
2788      res.add(b);
2789    return res;    
2790  }
2791
2792  private List<Base> funcHtmlChecks(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2793    // todo: actually check the HTML
2794    return makeBoolean(true);    
2795  }
2796
2797  
2798  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2799    List<Base> result = new ArrayList<Base>();
2800    if (exp.getParameters().size() == 1) {
2801      List<Base> pc = new ArrayList<Base>();
2802      boolean all = true;
2803      for (Base item : focus) {
2804        pc.clear();
2805        pc.add(item);
2806        Equality eq = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
2807        if (eq != Equality.True) {
2808          all = false;
2809          break;
2810        }
2811      }
2812      result.add(new BooleanType(all).noExtensions());
2813    } else {// (exp.getParameters().size() == 0) {
2814      boolean all = true;
2815      for (Base item : focus) {
2816        Equality eq = asBool(item);
2817        if (eq != Equality.True) {
2818          all = false;
2819          break;
2820        }
2821      }
2822      result.add(new BooleanType(all).noExtensions());
2823    }
2824    return result;
2825  }
2826
2827
2828  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
2829    return new ExecutionContext(context.appInfo, context.resource, context.context, context.aliases, newThis);
2830  }
2831
2832  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
2833    return new ExecutionTypeContext(context.appInfo, context.resource, context.context, newThis);
2834  }
2835
2836
2837  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2838    List<Base> result = new ArrayList<Base>();
2839    result.add(DateTimeType.now());
2840    return result;
2841  }
2842
2843
2844  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2845    List<Base> result = new ArrayList<Base>();
2846    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
2847    return result;
2848  }
2849
2850
2851  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2852    throw new Error("not Implemented yet");
2853  }
2854
2855
2856  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2857    List<Base> result = new ArrayList<Base>();
2858    List<Base> current = new ArrayList<Base>();
2859    current.addAll(focus);
2860    List<Base> added = new ArrayList<Base>();
2861    boolean more = true;
2862    while (more) {
2863      added.clear();
2864      for (Base item : current) {
2865        getChildrenByName(item, "*", added);
2866      }
2867      more = !added.isEmpty();
2868      result.addAll(added);
2869      current.clear();
2870      current.addAll(added);
2871    }
2872    return result;
2873  }
2874
2875
2876  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2877    List<Base> result = new ArrayList<Base>();
2878    for (Base b : focus)
2879      getChildrenByName(b, "*", result);
2880    return result;
2881  }
2882
2883
2884  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException {
2885    List<Base> result = new ArrayList<Base>();
2886
2887    if (focus.size() == 1) {
2888      String f = convertToString(focus.get(0));
2889
2890      if (!Utilities.noString(f)) {
2891
2892        if (exp.getParameters().size() != 2) {
2893
2894          String t = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2895          String r = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2896
2897          String n = f.replace(t, r);
2898          result.add(new StringType(n));
2899        }
2900        else {
2901          throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size()));
2902        }
2903      }
2904      else {
2905        throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item"));
2906      }
2907    }
2908    else {
2909      throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size()));
2910    }
2911    return result;
2912  }
2913
2914
2915  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2916    List<Base> result = new ArrayList<Base>();
2917    String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2918    String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2919
2920    if (focus.size() == 1 && !Utilities.noString(regex))
2921      result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)).noExtensions());
2922    else
2923      result.add(new StringType(convertToString(focus.get(0))).noExtensions());
2924    return result;
2925  }
2926
2927
2928  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2929    List<Base> result = new ArrayList<Base>();
2930    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2931
2932    if (focus.size() == 0)
2933      result.add(new BooleanType(false).noExtensions());
2934    else if (Utilities.noString(sw))
2935      result.add(new BooleanType(true).noExtensions());
2936    else {
2937      if (focus.size() == 1 && !Utilities.noString(sw))
2938        result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)).noExtensions());
2939      else
2940        result.add(new BooleanType(false).noExtensions());
2941    }
2942    return result;
2943  }
2944
2945
2946  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2947    List<Base> result = new ArrayList<Base>();
2948    result.add(new StringType(convertToString(focus)).noExtensions());
2949    return result;
2950  }
2951
2952  private List<Base> funcToBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2953    List<Base> result = new ArrayList<Base>();
2954    if (focus.size() == 1) {
2955      if (focus.get(0) instanceof BooleanType)
2956        result.add(focus.get(0));
2957      else if (focus.get(0) instanceof IntegerType) {
2958        int i = Integer.parseInt(focus.get(0).primitiveValue());
2959        if (i == 0)
2960          result.add(new BooleanType(false).noExtensions());
2961        else if (i == 1)
2962          result.add(new BooleanType(true).noExtensions());
2963      } else if (focus.get(0) instanceof DecimalType) {
2964        if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0)
2965          result.add(new BooleanType(false).noExtensions());
2966        else if (((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0)
2967          result.add(new BooleanType(true).noExtensions());
2968      } else if (focus.get(0) instanceof StringType) {
2969        if ("true".equalsIgnoreCase(focus.get(0).primitiveValue()))
2970          result.add(new BooleanType(true).noExtensions());
2971        else if ("false".equalsIgnoreCase(focus.get(0).primitiveValue()))
2972          result.add(new BooleanType(false).noExtensions()); 
2973      }
2974    }
2975    return result;
2976  }
2977
2978  private List<Base> funcToQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2979    List<Base> result = new ArrayList<Base>();
2980    if (focus.size() == 1) {
2981      if (focus.get(0) instanceof Quantity) 
2982        result.add(focus.get(0));
2983      else if (focus.get(0) instanceof StringType) {
2984        Quantity q = parseQuantityString(focus.get(0).primitiveValue());
2985        if (q != null)
2986          result.add(q.noExtensions());
2987      } else if (focus.get(0) instanceof IntegerType) {
2988        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
2989      } else if (focus.get(0) instanceof DecimalType) {
2990        result.add(new Quantity().setValue(new BigDecimal(focus.get(0).primitiveValue())).setSystem("http://unitsofmeasure.org").setCode("1").noExtensions());
2991      }
2992    }
2993    return result;
2994  }
2995
2996  private List<Base> funcToDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2997//  List<Base> result = new ArrayList<Base>();
2998//  result.add(new BooleanType(convertToBoolean(focus)));
2999//  return result;
3000  throw new NotImplementedException("funcToDateTime is not implemented");
3001}
3002
3003  private List<Base> funcToTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3004//  List<Base> result = new ArrayList<Base>();
3005//  result.add(new BooleanType(convertToBoolean(focus)));
3006//  return result;
3007  throw new NotImplementedException("funcToTime is not implemented");
3008}
3009
3010
3011  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3012    String s = convertToString(focus);
3013    List<Base> result = new ArrayList<Base>();
3014    if (Utilities.isDecimal(s))
3015      result.add(new DecimalType(s).noExtensions());
3016    if ("true".equals(s))
3017      result.add(new DecimalType(1).noExtensions());
3018    if ("false".equals(s))
3019      result.add(new DecimalType(0).noExtensions());
3020    return result;
3021  }
3022
3023
3024  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3025    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3026    Equality v = asBool(n1);
3027
3028    if (v == Equality.True)
3029      return execute(context, focus, exp.getParameters().get(1), true);
3030    else if (exp.getParameters().size() < 3)
3031      return new ArrayList<Base>();
3032    else
3033      return execute(context, focus, exp.getParameters().get(2), true);
3034  }
3035
3036
3037  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3038    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3039    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
3040
3041    List<Base> result = new ArrayList<Base>();
3042    for (int i = 0; i < Math.min(focus.size(), i1); i++)
3043      result.add(focus.get(i));
3044    return result;
3045  }
3046
3047
3048  private List<Base> funcUnion(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3049    List<Base> result = new ArrayList<Base>();
3050    for (Base item : focus) {
3051      if (!doContains(result, item))
3052        result.add(item);
3053    }
3054    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
3055      if (!doContains(result, item))
3056        result.add(item);
3057    }
3058    return result;
3059  }
3060
3061  private List<Base> funcCombine(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3062    List<Base> result = new ArrayList<Base>();
3063    for (Base item : focus) {
3064      result.add(item);
3065    }
3066    for (Base item : execute(context, focus, exp.getParameters().get(0), true)) {
3067      result.add(item);
3068    }
3069    return result;
3070  }
3071
3072  private List<Base> funcIntersect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3073    List<Base> result = new ArrayList<Base>();
3074    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
3075    
3076    for (Base item : focus) {
3077      if (!doContains(result, item) && doContains(other, item))
3078        result.add(item);
3079    }
3080    return result;    
3081  }
3082
3083  private List<Base> funcExclude(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3084    List<Base> result = new ArrayList<Base>();
3085    List<Base> other = execute(context, focus, exp.getParameters().get(0), true);
3086    
3087    for (Base item : focus) {
3088      if (!doContains(other, item))
3089        result.add(item);
3090    }
3091    return result;
3092  }
3093
3094
3095  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
3096    if (focus.size() == 1)
3097      return focus;
3098    throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()));
3099  }
3100
3101
3102  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
3103    if (focus.size() == 0 || focus.size() > 1) 
3104      return makeBoolean(false);
3105    String ns = null;
3106    String n = null;
3107    
3108    ExpressionNode texp = exp.getParameters().get(0);
3109    if (texp.getKind() != Kind.Name)
3110      throw new PathEngineException("Unsupported Expression type for Parameter on Is");
3111    if (texp.getInner() != null) {
3112      if (texp.getInner().getKind() != Kind.Name)
3113        throw new PathEngineException("Unsupported Expression type for Parameter on Is");
3114      ns = texp.getName();
3115      n = texp.getInner().getName();
3116    } else if (Utilities.existsInList(texp.getName(), "Boolean", "Integer", "Decimal", "String", "DateTime", "Time", "SimpleTypeInfo", "ClassInfo")) {
3117      ns = "System";
3118      n = texp.getName();
3119    } else {
3120      ns = "FHIR";
3121      n = texp.getName();        
3122    }
3123    if (ns.equals("System")) {
3124      if (focus.get(0) instanceof Resource)
3125        return makeBoolean(false);
3126      if (!(focus.get(0) instanceof Element) || ((Element) focus.get(0)).isDisallowExtensions())
3127        return makeBoolean(n.equals(Utilities.capitalize(focus.get(0).fhirType())));
3128      else
3129        return makeBoolean(false);
3130    } else if (ns.equals("FHIR")) {
3131      return makeBoolean(n.equals(focus.get(0).fhirType()));
3132    } else { 
3133      return makeBoolean(false);
3134    }
3135  }
3136
3137
3138  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3139    List<Base> result = new ArrayList<Base>();
3140    String tn;
3141    if (exp.getParameters().get(0).getInner() != null)
3142      tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
3143    else
3144      tn = "FHIR."+exp.getParameters().get(0).getName();
3145    for (Base b : focus) {
3146      if (tn.startsWith("System.")) {
3147          if (b instanceof Element &&((Element) b).isDisallowExtensions()) 
3148            if (b.hasType(tn.substring(7))) 
3149              result.add(b);
3150      } else if (tn.startsWith("FHIR.")) {
3151          if (b.hasType(tn.substring(5))) 
3152            result.add(b);
3153      }
3154    }
3155    return result;
3156  }
3157
3158  private List<Base> funcType(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3159    List<Base> result = new ArrayList<Base>();
3160    for (Base item : focus)
3161      result.add(new ClassTypeInfo(item));
3162    return result;
3163  }
3164
3165
3166  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3167    List<Base> result = new ArrayList<Base>();
3168    List<Base> current = new ArrayList<Base>();
3169    current.addAll(focus);
3170    List<Base> added = new ArrayList<Base>();
3171    boolean more = true;
3172    while (more) {
3173      added.clear();
3174      List<Base> pc = new ArrayList<Base>();
3175      for (Base item : current) {
3176        pc.clear();
3177        pc.add(item);
3178        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
3179      }
3180      more = !added.isEmpty();
3181      result.addAll(added);
3182      current.clear();
3183      current.addAll(added);
3184    }
3185    return result;
3186  }
3187
3188
3189  private List<Base> funcAggregate(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3190    List<Base> total = new ArrayList<Base>();
3191    if (exp.parameterCount() > 1)
3192      total = execute(context, focus, exp.getParameters().get(1), false);
3193
3194    List<Base> pc = new ArrayList<Base>();
3195    for (Base item : focus) {
3196      ExecutionContext c = changeThis(context, item);
3197      c.total = total;
3198      total = execute(c, pc, exp.getParameters().get(0), true);
3199    }
3200    return total;
3201  }
3202
3203
3204
3205  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3206    if (focus.size() < 1)
3207      return makeBoolean(true);
3208    if (focus.size() == 1)
3209      return makeBoolean(true);
3210
3211    boolean distinct = true;
3212    for (int i = 0; i < focus.size(); i++) {
3213      for (int j = i+1; j < focus.size(); j++) {
3214        Boolean eq = doEquals(focus.get(j), focus.get(i));
3215        if (eq == null) {
3216          return new ArrayList<Base>();
3217        } else if (eq == true) {
3218          distinct = false;
3219          break;
3220        }
3221      }
3222    }
3223    return makeBoolean(distinct);
3224  }
3225
3226
3227  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3228    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
3229
3230    boolean valid = true;
3231    for (Base item : target) {
3232      boolean found = false;
3233      for (Base t : focus) {
3234        if (Base.compareDeep(item, t, false)) {
3235          found = true;
3236          break;
3237        }
3238      }
3239      if (!found) {
3240        valid = false;
3241        break;
3242      }
3243    }
3244    List<Base> result = new ArrayList<Base>();
3245    result.add(new BooleanType(valid).noExtensions());
3246    return result;
3247  }
3248
3249
3250  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3251    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
3252
3253    boolean valid = true;
3254    for (Base item : focus) {
3255      boolean found = false;
3256      for (Base t : target) {
3257        if (Base.compareDeep(item, t, false)) {
3258          found = true;
3259          break;
3260        }
3261      }
3262      if (!found) {
3263        valid = false;
3264        break;
3265      }
3266    }
3267    List<Base> result = new ArrayList<Base>();
3268    result.add(new BooleanType(valid).noExtensions());
3269    return result;
3270  }
3271
3272
3273  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3274    List<Base> result = new ArrayList<Base>();
3275    boolean empty = true;
3276    for (Base f : focus)
3277      if (!f.isEmpty())
3278        empty = false;
3279    result.add(new BooleanType(!empty).noExtensions());
3280    return result;
3281  }
3282
3283
3284  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3285    List<Base> result = new ArrayList<Base>();
3286    for (Base item : focus) {
3287      String s = convertToString(item);
3288      if (item.fhirType().equals("Reference")) {
3289        Property p = item.getChildByName("reference");
3290        if (p != null && p.hasValues())
3291          s = convertToString(p.getValues().get(0));
3292        else
3293          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
3294      }
3295      if (item.fhirType().equals("canonical")) {
3296        s = item.primitiveValue();
3297      }
3298      if (s != null) {
3299        Base res = null;
3300        if (s.startsWith("#")) {
3301          Property p = context.resource.getChildByName("contained");
3302          for (Base c : p.getValues()) {
3303            if (chompHash(s).equals(chompHash(c.getIdBase()))) {
3304              res = c;
3305              break;
3306            }
3307          }
3308        } else if (hostServices != null) {
3309          res = hostServices.resolveReference(context.appInfo, s);
3310        }
3311        if (res != null)
3312          result.add(res);
3313      }
3314    }
3315
3316    return result;
3317  }
3318
3319  /**
3320   * Strips a leading hashmark (#) if present at the start of a string
3321   */
3322  private String chompHash(String theId) {
3323    String retVal = theId;
3324    while (retVal.startsWith("#")) {
3325      retVal = retVal.substring(1);
3326    }
3327    return retVal;
3328  }
3329
3330  private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3331    List<Base> result = new ArrayList<Base>();
3332    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3333    String url = nl.get(0).primitiveValue();
3334
3335    for (Base item : focus) {
3336      List<Base> ext = new ArrayList<Base>();
3337      getChildrenByName(item, "extension", ext);
3338      getChildrenByName(item, "modifierExtension", ext);
3339      for (Base ex : ext) {
3340        List<Base> vl = new ArrayList<Base>();
3341        getChildrenByName(ex, "url", vl);
3342        if (convertToString(vl).equals(url))
3343          result.add(ex);
3344      }
3345    }
3346    return result;
3347  }
3348
3349        private List<Base> funcAllFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3350          List<Base> result = new ArrayList<Base>();
3351          if (exp.getParameters().size() == 1) {
3352            boolean all = true;
3353            List<Base> pc = new ArrayList<Base>();
3354            for (Base item : focus) {
3355              pc.clear();
3356              pc.add(item);
3357              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
3358              Equality v = asBool(res);
3359              if (v != Equality.False) {
3360                all = false;
3361                break;
3362              }
3363            }
3364            result.add(new BooleanType(all).noExtensions());
3365          } else { 
3366            boolean all = true;
3367            for (Base item : focus) {
3368              Equality v = asBool(item);
3369        if (v != Equality.False) {
3370                all = false;
3371                break;
3372              }
3373            }
3374            result.add(new BooleanType(all).noExtensions());
3375          }
3376          return result;
3377        }
3378  
3379        private List<Base> funcAnyFalse(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3380          List<Base> result = new ArrayList<Base>();
3381          if (exp.getParameters().size() == 1) {
3382            boolean any = false;
3383            List<Base> pc = new ArrayList<Base>();
3384            for (Base item : focus) {
3385              pc.clear();
3386              pc.add(item);
3387              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
3388              Equality v = asBool(res);
3389        if (v == Equality.False) {
3390                any = true;
3391                break;
3392              }
3393            }
3394            result.add(new BooleanType(any).noExtensions());
3395          } else {
3396            boolean any = false;
3397            for (Base item : focus) {
3398              Equality v = asBool(item);
3399        if (v == Equality.False) {
3400                any = true;
3401                break;
3402              }
3403            }
3404            result.add(new BooleanType(any).noExtensions());
3405          }
3406          return result;
3407        }
3408  
3409        private List<Base> funcAllTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3410          List<Base> result = new ArrayList<Base>();
3411          if (exp.getParameters().size() == 1) {
3412            boolean all = true;
3413            List<Base> pc = new ArrayList<Base>();
3414            for (Base item : focus) {
3415              pc.clear();
3416              pc.add(item);
3417              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
3418              Equality v = asBool(res);
3419        if (v != Equality.True) {
3420                all = false;
3421                break;
3422              }
3423            }
3424            result.add(new BooleanType(all).noExtensions());
3425          } else { 
3426            boolean all = true;
3427            for (Base item : focus) {
3428              Equality v = asBool(item);
3429        if (v != Equality.True) {
3430                all = false;
3431                break;
3432              }
3433            }
3434            result.add(new BooleanType(all).noExtensions());
3435          }
3436          return result;
3437        }
3438
3439        private List<Base> funcAnyTrue(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3440          List<Base> result = new ArrayList<Base>();
3441          if (exp.getParameters().size() == 1) {
3442            boolean any = false;
3443            List<Base> pc = new ArrayList<Base>();
3444            for (Base item : focus) {
3445              pc.clear();
3446              pc.add(item);
3447              List<Base> res = execute(context, pc, exp.getParameters().get(0), true);
3448              Equality v = asBool(res);
3449        if (v == Equality.True) {
3450                any = true;
3451                break;
3452              }
3453            }
3454            result.add(new BooleanType(any).noExtensions());
3455          } else {
3456            boolean any = false;
3457      for (Base item : focus) {
3458        Equality v = asBool(item);
3459        if (v == Equality.True) {
3460                  any = true;
3461                  break;
3462                }
3463      }
3464      result.add(new BooleanType(any).noExtensions());
3465          }
3466          return result;
3467        }
3468
3469        private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3470    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
3471    String name = nl.get(0).primitiveValue();
3472    if (exp.getParameters().size() == 2) {
3473      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
3474      log(name, n2);
3475    } else 
3476      log(name, focus);
3477    return focus;
3478  }
3479
3480  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3481    if (focus.size() <= 1)
3482      return focus;
3483
3484    List<Base> result = new ArrayList<Base>();
3485    for (int i = 0; i < focus.size(); i++) {
3486      boolean found = false;
3487      for (int j = i+1; j < focus.size(); j++) {
3488        Boolean eq = doEquals(focus.get(j), focus.get(i));
3489        if (eq == null)
3490          return new ArrayList<Base>();
3491        else if (eq == true) {
3492          found = true;
3493          break;
3494        }
3495      }
3496      if (!found)
3497        result.add(focus.get(i));
3498    }
3499    return result;
3500  }
3501
3502        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3503    List<Base> result = new ArrayList<Base>();
3504    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3505
3506    if (focus.size() == 1 && !Utilities.noString(sw)) {
3507      String st = convertToString(focus.get(0));
3508      if (Utilities.noString(st))
3509        result.add(new BooleanType(false).noExtensions());
3510      else
3511        result.add(new BooleanType(st.matches(sw)).noExtensions());
3512    } else
3513      result.add(new BooleanType(false).noExtensions());
3514    return result;
3515  }
3516
3517        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3518    List<Base> result = new ArrayList<Base>();
3519    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3520
3521    if (focus.size() != 1) {
3522      result.add(new BooleanType(false).noExtensions());
3523    } else if (Utilities.noString(sw)) {
3524      result.add(new BooleanType(true).noExtensions());
3525    } else {
3526      String st = convertToString(focus.get(0));
3527      if (Utilities.noString(st))
3528        result.add(new BooleanType(false).noExtensions());
3529      else
3530        result.add(new BooleanType(st.contains(sw)).noExtensions());
3531    } 
3532    return result;
3533  }
3534
3535  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3536    List<Base> result = new ArrayList<Base>();
3537    if (focus.size() == 1) {
3538      String s = convertToString(focus.get(0));
3539      result.add(new IntegerType(s.length()).noExtensions());
3540    }
3541    return result;
3542  }
3543
3544  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3545    List<Base> result = new ArrayList<Base>();
3546    if (focus.size() == 1) {
3547      String s = convertToString(focus.get(0));
3548      result.add(new BooleanType(!Utilities.noString(s)).noExtensions());
3549    } else
3550      result.add(new BooleanType(false).noExtensions());
3551    return result;
3552  }
3553
3554        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3555    List<Base> result = new ArrayList<Base>();
3556    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3557
3558    if (focus.size() == 0) {
3559      result.add(new BooleanType(false).noExtensions());
3560    } else if (Utilities.noString(sw)) {
3561      result.add(new BooleanType(true).noExtensions());
3562    } else {
3563      String s = convertToString(focus.get(0));
3564      if (s == null)
3565        result.add(new BooleanType(false).noExtensions());
3566      else
3567        result.add(new BooleanType(s.startsWith(sw)).noExtensions());
3568    }
3569    return result;
3570  }
3571
3572  private List<Base> funcLower(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3573    List<Base> result = new ArrayList<Base>();
3574    if (focus.size() == 1) {
3575      String s = convertToString(focus.get(0));
3576      if (!Utilities.noString(s)) 
3577        result.add(new StringType(s.toLowerCase()).noExtensions());
3578    }
3579    return result;
3580  }
3581
3582  private List<Base> funcUpper(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3583    List<Base> result = new ArrayList<Base>();
3584    if (focus.size() == 1) {
3585      String s = convertToString(focus.get(0));
3586      if (!Utilities.noString(s)) 
3587        result.add(new StringType(s.toUpperCase()).noExtensions());
3588    }
3589    return result;
3590  }
3591
3592  private List<Base> funcToChars(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3593    List<Base> result = new ArrayList<Base>();
3594    if (focus.size() == 1) {
3595      String s = convertToString(focus.get(0));
3596      for (char c : s.toCharArray())  
3597        result.add(new StringType(String.valueOf(c)).noExtensions());
3598    }
3599    return result;
3600  }
3601
3602        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3603    List<Base> result = new ArrayList<Base>();
3604    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3605    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
3606    int i2 = -1;
3607    if (exp.parameterCount() == 2) {
3608      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
3609      i2 = Integer.parseInt(n2.get(0).primitiveValue());
3610    }
3611
3612    if (focus.size() == 1) {
3613      String sw = convertToString(focus.get(0));
3614      String s;
3615      if (i1 < 0 || i1 >= sw.length())
3616        return new ArrayList<Base>();
3617      if (exp.parameterCount() == 2)
3618        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
3619      else
3620        s = sw.substring(i1);
3621      if (!Utilities.noString(s)) 
3622        result.add(new StringType(s).noExtensions());
3623    }
3624    return result;
3625  }
3626
3627  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3628    String s = convertToString(focus);
3629    List<Base> result = new ArrayList<Base>();
3630    if (Utilities.isInteger(s))
3631      result.add(new IntegerType(s).noExtensions());
3632    else if ("true".equals(s))
3633      result.add(new IntegerType(1).noExtensions());
3634    else if ("false".equals(s))
3635      result.add(new IntegerType(0).noExtensions());
3636    return result;
3637  }
3638
3639  private List<Base> funcIsInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3640    List<Base> result = new ArrayList<Base>();
3641    if (focus.size() != 1)
3642      result.add(new BooleanType(false).noExtensions());
3643    else if (focus.get(0) instanceof IntegerType)
3644      result.add(new BooleanType(true).noExtensions());
3645    else if (focus.get(0) instanceof BooleanType)
3646      result.add(new BooleanType(true).noExtensions());
3647    else if (focus.get(0) instanceof StringType)
3648      result.add(new BooleanType(Utilities.isInteger(convertToString(focus.get(0)))).noExtensions());
3649    else 
3650      result.add(new BooleanType(false).noExtensions());
3651    return result;
3652  }
3653
3654  private List<Base> funcIsBoolean(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3655    List<Base> result = new ArrayList<Base>();
3656    if (focus.size() != 1)
3657      result.add(new BooleanType(false).noExtensions());
3658    else if (focus.get(0) instanceof IntegerType)
3659      result.add(new BooleanType(((IntegerType) focus.get(0)).getValue() >= 0 && ((IntegerType) focus.get(0)).getValue() <= 1).noExtensions());
3660    else if (focus.get(0) instanceof DecimalType)
3661      result.add(new BooleanType(((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ZERO) == 0 || ((DecimalType) focus.get(0)).getValue().compareTo(BigDecimal.ONE) == 0).noExtensions());
3662    else if (focus.get(0) instanceof BooleanType)
3663      result.add(new BooleanType(true).noExtensions());
3664    else if (focus.get(0) instanceof StringType)
3665      result.add(new BooleanType(Utilities.existsInList(convertToString(focus.get(0)).toLowerCase(), "true", "false")).noExtensions());
3666    else 
3667      result.add(new BooleanType(false).noExtensions());
3668    return result;
3669  }
3670
3671  private List<Base> funcIsDateTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3672    List<Base> result = new ArrayList<Base>();
3673    if (focus.size() != 1)
3674      result.add(new BooleanType(false).noExtensions());
3675    else if (focus.get(0) instanceof DateTimeType || focus.get(0) instanceof DateType)
3676      result.add(new BooleanType(true).noExtensions());
3677    else if (focus.get(0) instanceof StringType)
3678      result.add(new BooleanType((convertToString(focus.get(0)).matches
3679          ("([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());
3680    else 
3681      result.add(new BooleanType(false).noExtensions());
3682    return result;
3683  }
3684
3685  private List<Base> funcConformsTo(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3686    if (hostServices == null)
3687      throw new FHIRException("Unable to check conformsTo - no hostservices provided");
3688    List<Base> result = new ArrayList<Base>();
3689    if (focus.size() != 1)
3690      result.add(new BooleanType(false).noExtensions());
3691    else {
3692      String url = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3693      result.add(new BooleanType(hostServices.conformsToProfile(context.appInfo,  focus.get(0), url)).noExtensions());
3694    }
3695    return result;
3696  }
3697
3698  private List<Base> funcIsTime(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3699    List<Base> result = new ArrayList<Base>();
3700    if (focus.size() != 1)
3701      result.add(new BooleanType(false).noExtensions());
3702    else if (focus.get(0) instanceof TimeType)
3703      result.add(new BooleanType(true).noExtensions());
3704    else if (focus.get(0) instanceof StringType)
3705      result.add(new BooleanType((convertToString(focus.get(0)).matches
3706          ("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());
3707    else 
3708      result.add(new BooleanType(false).noExtensions());
3709    return result;
3710  }
3711
3712  private List<Base> funcIsString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3713    List<Base> result = new ArrayList<Base>();
3714    if (focus.size() != 1)
3715      result.add(new BooleanType(false).noExtensions());
3716    else if (!(focus.get(0) instanceof DateTimeType) && !(focus.get(0) instanceof TimeType))
3717      result.add(new BooleanType(true).noExtensions());
3718    else 
3719      result.add(new BooleanType(false).noExtensions());
3720    return result;
3721  }
3722
3723  private List<Base> funcIsQuantity(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3724    List<Base> result = new ArrayList<Base>();
3725    if (focus.size() != 1)
3726      result.add(new BooleanType(false).noExtensions());
3727    else if (focus.get(0) instanceof IntegerType)
3728      result.add(new BooleanType(true).noExtensions());
3729    else if (focus.get(0) instanceof DecimalType)
3730      result.add(new BooleanType(true).noExtensions());
3731    else if (focus.get(0) instanceof Quantity)
3732      result.add(new BooleanType(true).noExtensions());
3733    else if (focus.get(0) instanceof BooleanType)
3734      result.add(new BooleanType(true).noExtensions());
3735    else  if (focus.get(0) instanceof StringType) {
3736      Quantity q = parseQuantityString(focus.get(0).primitiveValue());
3737      result.add(new BooleanType(q != null).noExtensions());
3738    } else
3739      result.add(new BooleanType(false).noExtensions());
3740    return result;
3741  }
3742
3743  public Quantity parseQuantityString(String s) {
3744    if (s == null)
3745      return null;
3746    s = s.trim();
3747    if (s.contains(" ")) {
3748      String v = s.substring(0, s.indexOf(" ")).trim();
3749      s = s.substring(s.indexOf(" ")).trim();
3750      if (!Utilities.isDecimal(v))
3751        return null;
3752      if (s.startsWith("'") && s.endsWith("'"))
3753        return Quantity.fromUcum(v, s.substring(1, s.length()-1));
3754      if (s.equals("year") || s.equals("years"))
3755        return Quantity.fromUcum(v, "a");
3756      else if (s.equals("month") || s.equals("months"))
3757        return Quantity.fromUcum(v, "mo");
3758      else if (s.equals("week") || s.equals("weeks"))
3759        return Quantity.fromUcum(v, "wk");
3760      else if (s.equals("day") || s.equals("days"))
3761        return Quantity.fromUcum(v, "d");
3762      else if (s.equals("hour") || s.equals("hours"))
3763        return Quantity.fromUcum(v, "h");
3764      else if (s.equals("minute") || s.equals("minutes"))
3765        return Quantity.fromUcum(v, "min");
3766      else if (s.equals("second") || s.equals("seconds"))
3767        return Quantity.fromUcum(v, "s");
3768      else if (s.equals("millisecond") || s.equals("milliseconds"))
3769        return Quantity.fromUcum(v, "ms");
3770      else
3771        return null;      
3772    } else {
3773      if (Utilities.isDecimal(s))
3774        return new Quantity().setValue(new BigDecimal(s)).setSystem("http://unitsofmeasure.org").setCode("1");
3775      else
3776        return null;
3777    }
3778  }
3779
3780
3781  private List<Base> funcIsDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3782    List<Base> result = new ArrayList<Base>();
3783    if (focus.size() != 1)
3784      result.add(new BooleanType(false).noExtensions());
3785    else if (focus.get(0) instanceof IntegerType)
3786      result.add(new BooleanType(true).noExtensions());
3787    else if (focus.get(0) instanceof BooleanType)
3788      result.add(new BooleanType(true).noExtensions());
3789    else if (focus.get(0) instanceof DecimalType)
3790      result.add(new BooleanType(true).noExtensions());
3791    else if (focus.get(0) instanceof StringType)
3792      result.add(new BooleanType(Utilities.isDecimal(convertToString(focus.get(0)))).noExtensions());
3793    else 
3794      result.add(new BooleanType(false).noExtensions());
3795    return result;
3796  }
3797
3798  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3799    List<Base> result = new ArrayList<Base>();
3800    result.add(new IntegerType(focus.size()).noExtensions());
3801    return result;
3802  }
3803
3804  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3805    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
3806    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
3807
3808    List<Base> result = new ArrayList<Base>();
3809    for (int i = i1; i < focus.size(); i++)
3810      result.add(focus.get(i));
3811    return result;
3812  }
3813
3814  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3815    List<Base> result = new ArrayList<Base>();
3816    for (int i = 1; i < focus.size(); i++)
3817      result.add(focus.get(i));
3818    return result;
3819  }
3820
3821  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3822    List<Base> result = new ArrayList<Base>();
3823    if (focus.size() > 0)
3824      result.add(focus.get(focus.size()-1));
3825    return result;
3826  }
3827
3828  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3829    List<Base> result = new ArrayList<Base>();
3830    if (focus.size() > 0)
3831      result.add(focus.get(0));
3832    return result;
3833  }
3834
3835
3836        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3837    List<Base> result = new ArrayList<Base>();
3838    List<Base> pc = new ArrayList<Base>();
3839    for (Base item : focus) {
3840      pc.clear();
3841      pc.add(item);
3842      Equality v = asBool(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
3843      if (v == Equality.True)
3844        result.add(item);
3845    }
3846    return result;
3847  }
3848
3849  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3850    List<Base> result = new ArrayList<Base>();
3851    List<Base> pc = new ArrayList<Base>();
3852    for (Base item : focus) {
3853      pc.clear();
3854      pc.add(item);
3855      result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
3856    }
3857    return result;
3858  }
3859
3860
3861        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
3862    List<Base> result = new ArrayList<Base>();
3863    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
3864    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size())
3865      result.add(focus.get(Integer.parseInt(s)));
3866    return result;
3867  }
3868
3869  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
3870    List<Base> result = new ArrayList<Base>();
3871                result.add(new BooleanType(ElementUtil.isEmpty(focus)).noExtensions());
3872    return result;
3873  }
3874
3875  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
3876    List<Base> result = new ArrayList<Base>();  
3877    Equality v = asBool(focus);
3878    if (v != Equality.Null)
3879      result.add(new BooleanType(v != Equality.True));
3880    return result;
3881  }
3882
3883  public class ElementDefinitionMatch {
3884    private ElementDefinition definition;
3885    private String fixedType;
3886    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
3887      super();
3888      this.definition = definition;
3889      this.fixedType = fixedType;
3890    }
3891    public ElementDefinition getDefinition() {
3892      return definition;
3893    }
3894    public String getFixedType() {
3895      return fixedType;
3896    }
3897
3898  }
3899
3900  private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException {
3901    if (Utilities.noString(type))
3902      throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName");
3903    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml"))
3904      return;
3905    if (type.equals(TypeDetails.FP_SimpleTypeInfo)) { 
3906      getSimpleTypeChildTypesByName(name, result);
3907    } else if (type.equals(TypeDetails.FP_ClassInfo)) { 
3908      getClassInfoChildTypesByName(name, result);
3909    } else {
3910    String url = null;
3911    if (type.contains("#")) {
3912      url = type.substring(0, type.indexOf("#"));
3913    } else {
3914      url = type;
3915    }
3916    String tail = "";
3917    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
3918    if (sd == null)
3919      throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong
3920    List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
3921    ElementDefinitionMatch m = null;
3922    if (type.contains("#"))
3923      m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false);
3924    if (m != null && hasDataType(m.definition)) {
3925      if (m.fixedType != null)
3926      {
3927        StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(m.fixedType, worker.getOverrideVersionNs()));
3928        if (dt == null)
3929          throw new DefinitionException("unknown data type "+m.fixedType);
3930        sdl.add(dt);
3931      } else
3932        for (TypeRefComponent t : m.definition.getType()) {
3933          StructureDefinition dt = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(t.getCode(), worker.getOverrideVersionNs()));
3934          if (dt == null)
3935            throw new DefinitionException("unknown data type "+t.getCode());
3936          sdl.add(dt);
3937        }
3938    } else {
3939      sdl.add(sd);
3940      if (type.contains("#")) {
3941        tail = type.substring(type.indexOf("#")+1);
3942        tail = tail.substring(tail.indexOf("."));
3943      }
3944    }
3945
3946    for (StructureDefinition sdi : sdl) {
3947      String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
3948      if (name.equals("**")) {
3949        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
3950        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
3951          if (ed.getPath().startsWith(path))
3952            for (TypeRefComponent t : ed.getType()) {
3953              if (t.hasCode() && t.getCodeElement().hasValue()) {
3954                String tn = null;
3955                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
3956                  tn = sdi.getType()+"#"+ed.getPath();
3957                else
3958                  tn = t.getCode();
3959                if (t.getCode().equals("Resource")) {
3960                  for (String rn : worker.getResourceNames()) {
3961                    if (!result.hasType(worker, rn)) {
3962                      getChildTypesByName(result.addType(rn), "**", result);
3963                    }                  
3964                  }
3965                } else if (!result.hasType(worker, tn)) {
3966                  getChildTypesByName(result.addType(tn), "**", result);
3967                }
3968              }
3969            }
3970        }      
3971      } else if (name.equals("*")) {
3972        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
3973        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
3974          if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
3975            for (TypeRefComponent t : ed.getType()) {
3976              if (Utilities.noString(t.getCode())) // Element.id or Extension.url
3977                result.addType("System.string");
3978              else if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
3979                result.addType(sdi.getType()+"#"+ed.getPath());
3980              else if (t.getCode().equals("Resource"))
3981                result.addTypes(worker.getResourceNames());
3982              else
3983                result.addType(t.getCode());
3984            }
3985        }
3986      } else {
3987        path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
3988
3989        ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
3990        if (ed != null) {
3991          if (!Utilities.noString(ed.getFixedType()))
3992            result.addType(ed.getFixedType());
3993          else
3994            for (TypeRefComponent t : ed.getDefinition().getType()) {
3995              if (Utilities.noString(t.getCode())) {
3996                if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url")) 
3997                  result.addType(TypeDetails.FP_NS, "string");
3998                break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
3999              }
4000
4001              ProfiledType pt = null;
4002              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
4003                pt = new ProfiledType(sdi.getUrl()+"#"+path);
4004              else if (t.getCode().equals("Resource"))
4005                result.addTypes(worker.getResourceNames());
4006              else
4007                pt = new ProfiledType(t.getCode());
4008              if (pt != null) {
4009                if (t.hasProfile())
4010                  pt.addProfiles(t.getProfile());
4011                if (ed.getDefinition().hasBinding())
4012                  pt.addBinding(ed.getDefinition().getBinding());
4013                result.addType(pt);
4014              }
4015            }
4016        }
4017      }
4018    }
4019    }
4020  }
4021
4022  private void getClassInfoChildTypesByName(String name, TypeDetails result) {
4023    if (name.equals("namespace"))
4024      result.addType(TypeDetails.FP_String);
4025    if (name.equals("name"))
4026      result.addType(TypeDetails.FP_String);
4027  }
4028
4029
4030  private void getSimpleTypeChildTypesByName(String name, TypeDetails result) {
4031    if (name.equals("namespace"))
4032      result.addType(TypeDetails.FP_String);
4033    if (name.equals("name"))
4034      result.addType(TypeDetails.FP_String);
4035  }
4036
4037
4038  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException {
4039    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
4040      if (ed.getPath().equals(path)) {
4041        if (ed.hasContentReference()) {
4042          return getElementDefinitionById(sd, ed.getContentReference());
4043        } else
4044          return new ElementDefinitionMatch(ed, null);
4045      }
4046      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3)
4047        return new ElementDefinitionMatch(ed, null);
4048      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
4049        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
4050        if (primitiveTypes.contains(s))
4051          return new ElementDefinitionMatch(ed, s);
4052        else
4053        return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
4054      }
4055      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) { 
4056        // now we walk into the type.
4057        if (ed.getType().size() > 1)  // if there's more than one type, the test above would fail this
4058          throw new PathEngineException("Internal typing issue....");
4059        StructureDefinition nsd = worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs()));
4060            if (nsd == null) 
4061              throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode());
4062        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName);
4063      }
4064      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
4065        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
4066        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName);
4067      }
4068    }
4069    return null;
4070  }
4071
4072  private boolean isAbstractType(List<TypeRefComponent> list) {
4073        return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
4074}
4075
4076
4077  private boolean hasType(ElementDefinition ed, String s) {
4078    for (TypeRefComponent t : ed.getType()) 
4079      if (s.equalsIgnoreCase(t.getCode()))
4080        return true;
4081    return false;
4082  }
4083
4084  private boolean hasDataType(ElementDefinition ed) {
4085    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
4086  }
4087
4088  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
4089    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
4090      if (ref.equals("#"+ed.getId())) 
4091        return new ElementDefinitionMatch(ed, null);
4092    }
4093    return null;
4094  }
4095
4096
4097  public boolean hasLog() {
4098    return log != null && log.length() > 0;
4099  }
4100
4101
4102  public String takeLog() {
4103    if (!hasLog())
4104      return "";
4105    String s = log.toString();
4106    log = new StringBuilder();
4107    return s;
4108  }
4109
4110
4111  /** given an element definition in a profile, what element contains the differentiating fixed 
4112   * for the element, given the differentiating expresssion. The expression is only allowed to 
4113   * use a subset of FHIRPath
4114   * 
4115   * @param profile
4116   * @param element
4117   * @return
4118   * @throws PathEngineException 
4119   * @throws DefinitionException 
4120   */
4121  public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException {
4122    StructureDefinition sd = profile;
4123    ElementDefinition focus = null;
4124
4125    if (expr.getKind() == Kind.Name) {
4126      List<ElementDefinition> childDefinitions;
4127      childDefinitions = ProfileUtilities.getChildMap(sd, element);
4128      // if that's empty, get the children of the type
4129      if (childDefinitions.isEmpty()) {
4130        sd = fetchStructureByType(element);
4131        if (sd == null)
4132          throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getProfile()+"' on "+element.getId()+" could not be resolved");
4133        childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
4134      }
4135      for (ElementDefinition t : childDefinitions) {
4136        if (tailMatches(t, expr.getName())) {
4137          focus = t;
4138          break;
4139        }
4140      }
4141    } else if (expr.getKind() == Kind.Function) {
4142      if ("resolve".equals(expr.getName())) {
4143        if (!element.hasType())
4144          throw new DefinitionException("illegal use of resolve() in discriminator - no type on element "+element.getId());
4145        if (element.getType().size() > 1)
4146          throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible types on "+element.getId());
4147        if (!element.getType().get(0).hasTarget())
4148          throw new DefinitionException("illegal use of resolve() in discriminator - type on "+element.getId()+" is not Reference ("+element.getType().get(0).getCode()+")");
4149        if (element.getType().get(0).getTargetProfile().size() > 1)
4150          throw new DefinitionException("illegal use of resolve() in discriminator - Multiple possible target type profiles on "+element.getId());
4151        sd = worker.fetchResource(StructureDefinition.class, element.getType().get(0).getTargetProfile().get(0).getValue());
4152        if (sd == null)
4153          throw new DefinitionException("Problem with use of resolve() - profile '"+element.getType().get(0).getTargetProfile()+"' on "+element.getId()+" could not be resolved");
4154        focus = sd.getSnapshot().getElementFirstRep();
4155      } else if ("extension".equals(expr.getName())) {
4156        String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue();
4157//        targetUrl = targetUrl.substring(1,targetUrl.length()-1);
4158        List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(sd, element);
4159        for (ElementDefinition t : childDefinitions) {
4160          if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
4161           sd = worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue());
4162           while (sd!=null && !sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension"))
4163             sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
4164           if (sd.getUrl().equals(targetUrl)) {
4165             focus = t;
4166             break;
4167           }
4168          }
4169        }
4170      } else 
4171        throw new DefinitionException("illegal function name "+expr.getName()+"() in discriminator");
4172    } else if (expr.getKind() == Kind.Group) {
4173      throw new DefinitionException("illegal expression syntax in discriminator (group)");
4174    } else if (expr.getKind() == Kind.Constant) {
4175      throw new DefinitionException("illegal expression syntax in discriminator (const)");
4176    }
4177
4178    if (focus == null)
4179      throw new DefinitionException("Unable to resolve discriminator");      
4180    else if (expr.getInner() == null)
4181      return focus;
4182    else
4183      return evaluateDefinition(expr.getInner(), sd, focus);
4184  }
4185
4186  private StructureDefinition fetchStructureByType(ElementDefinition ed) throws DefinitionException {
4187    if (ed.getType().size() == 0)
4188      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, no type");
4189    if (ed.getType().size() > 1)
4190      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple types");
4191    if (ed.getType().get(0).getProfile().size() > 1)
4192      throw new DefinitionException("Error in discriminator at "+ed.getId()+": no children, multiple type profiles");
4193    if (ed.hasSlicing()) 
4194      throw new DefinitionException("Error in discriminator at "+ed.getId()+": slicing found");
4195    if (ed.getType().get(0).hasProfile()) 
4196      return worker.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue());
4197    else
4198      return worker.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(ed.getType().get(0).getCode(), worker.getOverrideVersionNs()));
4199  }
4200
4201
4202  private boolean tailMatches(ElementDefinition t, String d) {
4203    String tail = tailDot(t.getPath());
4204    if (d.contains("["))
4205      return tail.startsWith(d.substring(0, d.indexOf('[')));
4206    else if (tail.equals(d))
4207      return true;
4208    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()))
4209      return tail.startsWith(d);
4210    
4211    return false;
4212  }
4213
4214  private String tailDot(String path) {
4215    return path.substring(path.lastIndexOf(".") + 1);
4216  }
4217
4218  private Equality asBool(List<Base> items) throws PathEngineException {
4219    if (items.size() == 0)
4220      return Equality.Null;
4221    else if (items.size() == 1)
4222      return asBool(items.get(0));
4223    else
4224      throw new PathEngineException("Unable to evaluate as a boolean: "+convertToString(items));
4225  }
4226  
4227  private Equality asBoolFromInt(String s) {
4228    try {
4229      int i = Integer.parseInt(s);
4230      switch (i) {
4231      case 0: return Equality.False;
4232      case 1: return Equality.True;
4233      default: return Equality.Null;
4234      }
4235    } catch (Exception e) {
4236      return Equality.Null;
4237    }
4238  }
4239
4240  private Equality asBoolFromDec(String s) {
4241    try {
4242      BigDecimal d = new BigDecimal(s);
4243      if (d.compareTo(BigDecimal.ZERO) == 0) 
4244        return Equality.False;
4245      else if (d.compareTo(BigDecimal.ONE) == 0) 
4246        return Equality.True;
4247      else
4248        return Equality.Null;
4249    } catch (Exception e) {
4250      return Equality.Null;
4251    }
4252  }
4253
4254  private Equality asBool(Base item) {
4255    if (item instanceof BooleanType) 
4256      return boolToTriState(((BooleanType) item).booleanValue());
4257    else if (item.isBooleanPrimitive()) {
4258      if (Utilities.existsInList(item.primitiveValue(), "true"))
4259        return Equality.True;
4260      else if (Utilities.existsInList(item.primitiveValue(), "false"))
4261        return Equality.False;
4262      else
4263        return Equality.Null;
4264    } else if (item instanceof IntegerType || Utilities.existsInList(item.fhirType(), "integer", "positiveint", "unsignedInt"))
4265      return asBoolFromInt(item.primitiveValue());
4266    else if (item instanceof DecimalType || Utilities.existsInList(item.fhirType(), "decimal"))
4267      return asBoolFromDec(item.primitiveValue());
4268    else if (Utilities.existsInList(item.fhirType(), FHIR_TYPES_STRING)) {
4269      if (Utilities.existsInList(item.primitiveValue(), "true", "t", "yes", "y"))
4270        return Equality.True;
4271      else if (Utilities.existsInList(item.primitiveValue(), "false", "f", "no", "n"))
4272        return Equality.False;
4273      else if (Utilities.isInteger(item.primitiveValue()))
4274        return asBoolFromInt(item.primitiveValue());
4275      else if (Utilities.isDecimal(item.primitiveValue()))
4276        return asBoolFromDec(item.primitiveValue());
4277      else
4278        return Equality.Null;
4279    } 
4280      return Equality.Null;
4281  }
4282          
4283  private Equality boolToTriState(boolean b) {
4284    return b ? Equality.True : Equality.False;
4285  }
4286  
4287}