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