001package org.hl7.fhir.dstu3.fhirpath;
002
003import java.math.BigDecimal;
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.EnumSet;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012
013import org.fhir.ucum.Decimal;
014import org.fhir.ucum.UcumException;
015import org.hl7.fhir.dstu3.context.IWorkerContext;
016import org.hl7.fhir.dstu3.fhirpath.ExpressionNode.CollectionStatus;
017import org.hl7.fhir.dstu3.fhirpath.ExpressionNode.Function;
018import org.hl7.fhir.dstu3.fhirpath.ExpressionNode.Kind;
019import org.hl7.fhir.dstu3.fhirpath.ExpressionNode.Operation;
020import org.hl7.fhir.dstu3.fhirpath.ExpressionNode.SourceLocation;
021import org.hl7.fhir.dstu3.fhirpath.FHIRLexer.FHIRLexerException;
022import org.hl7.fhir.dstu3.fhirpath.FHIRPathUtilityClasses.ExecutionContext;
023import org.hl7.fhir.dstu3.fhirpath.FHIRPathUtilityClasses.ExecutionTypeContext;
024import org.hl7.fhir.dstu3.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
025import org.hl7.fhir.dstu3.fhirpath.TypeDetails.ProfiledType;
026import org.hl7.fhir.dstu3.model.Base;
027import org.hl7.fhir.dstu3.model.BooleanType;
028import org.hl7.fhir.dstu3.model.DateTimeType;
029import org.hl7.fhir.dstu3.model.DateType;
030import org.hl7.fhir.dstu3.model.DecimalType;
031import org.hl7.fhir.dstu3.model.ElementDefinition;
032import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
033import org.hl7.fhir.dstu3.model.IntegerType;
034import org.hl7.fhir.dstu3.model.Property;
035import org.hl7.fhir.dstu3.model.Resource;
036import org.hl7.fhir.dstu3.model.StringType;
037import org.hl7.fhir.dstu3.model.StructureDefinition;
038import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
039import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
040import org.hl7.fhir.dstu3.model.TimeType;
041import org.hl7.fhir.exceptions.DefinitionException;
042import org.hl7.fhir.exceptions.FHIRException;
043import org.hl7.fhir.exceptions.PathEngineException;
044import org.hl7.fhir.utilities.Utilities;
045
046/*
047  Copyright (c) 2011+, HL7, Inc.
048  All rights reserved.
049  
050  Redistribution and use in source and binary forms, with or without modification, 
051  are permitted provided that the following conditions are met:
052    
053   * Redistributions of source code must retain the above copyright notice, this 
054     list of conditions and the following disclaimer.
055   * Redistributions in binary form must reproduce the above copyright notice, 
056     this list of conditions and the following disclaimer in the documentation 
057     and/or other materials provided with the distribution.
058   * Neither the name of HL7 nor the names of its contributors may be used to 
059     endorse or promote products derived from this software without specific 
060     prior written permission.
061  
062  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
063  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
064  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
065  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
066  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
067  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
068  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
069  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
070  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
071  POSSIBILITY OF SUCH DAMAGE.
072  
073 */
074
075
076
077import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
078import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
079import ca.uhn.fhir.util.ElementUtil;
080
081/**
082 *
083 * @author Grahame Grieve
084 *
085 */
086public class FHIRPathEngine {
087  private IWorkerContext worker;
088  private IEvaluationContext hostServices;
089  private StringBuilder log = new StringBuilder();
090  private Set<String> primitiveTypes = new HashSet<String>();
091  private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
092
093  // if the fhir path expressions are allowed to use constants beyond those defined in the specification
094  // the application can implement them by providing a constant resolver
095  public interface IEvaluationContext {
096   
097
098    /**
099     * A constant reference - e.g. a reference to a name that must be resolved in context.
100     * The % will be removed from the constant name before this is invoked.
101     *
102     * This will also be called if the host invokes the FluentPath engine with a context of null
103     *
104     * @param appContext - content passed into the fluent path engine
105     * @param name - name reference to resolve
106     * @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
107     */
108    public Base resolveConstant(Object appContext, String name)  throws PathEngineException;
109    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException;
110
111    /**
112     * when the .log() function is called
113     *
114     * @param argument
115     * @param focus
116     * @return
117     */
118    public boolean log(String argument, List<Base> focus);
119
120    // extensibility for functions
121    /**
122     *
123     * @param functionName
124     * @return null if the function is not known
125     */
126    public FunctionDetails resolveFunction(String functionName);
127
128    /**
129     * Check the function parameters, and throw an error if they are incorrect, or return the type for the function
130     * @param functionName
131     * @param parameters
132     * @return
133     */
134    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException;
135
136    /**
137     * @param appContext
138     * @param functionName
139     * @param parameters
140     * @return
141     */
142    public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters);
143
144    /**
145     * Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
146     * @param url
147     * @return
148     */
149    public Base resolveReference(Object appContext, String url);
150  }
151
152
153  /**
154   * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
155   */
156  public FHIRPathEngine(IWorkerContext worker) {
157    super();
158    this.worker = worker;
159    for (StructureDefinition sd : worker.allStructures()) {
160      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
161        allTypes.put(sd.getName(), sd);
162      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
163        primitiveTypes.add(sd.getName());
164      }
165    }
166  }
167
168
169  // --- 3 methods to override in children -------------------------------------------------------
170  // if you don't override, it falls through to the using the base reference implementation
171  // HAPI overrides to these to support extending the base model
172
173  public IEvaluationContext getHostServices() {
174    return hostServices;
175  }
176
177
178  public void setHostServices(IEvaluationContext constantResolver) {
179    this.hostServices = constantResolver;
180  }
181
182
183  /**
184   * Given an item, return all the children that conform to the pattern described in name
185   *
186   * Possible patterns:
187   *  - a simple name (which may be the base of a name with [] e.g. value[x])
188   *  - a name with a type replacement e.g. valueCodeableConcept
189   *  - * which means all children
190   *  - ** which means all descendants
191   *
192   * @param item
193   * @param name
194   * @param result
195         * @throws FHIRException
196   */
197  protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
198        Base[] list = item.listChildrenByName(name, false);
199        if (list != null)
200                for (Base v : list)
201      if (v != null)
202        result.add(v);
203  }
204
205  // --- public API -------------------------------------------------------
206  /**
207   * Parse a path for later use using execute
208   *
209   * @param path
210   * @return
211   * @throws PathEngineException
212   * @throws Exception
213   */
214  public ExpressionNode parse(String path) throws FHIRLexerException {
215    FHIRLexer lexer = new FHIRLexer(path);
216    if (lexer.done())
217      throw lexer.error("Path cannot be empty");
218    ExpressionNode result = parseExpression(lexer, true);
219    if (!lexer.done())
220      throw lexer.error("Premature ExpressionNode termination at unexpected token \""+lexer.getCurrent()+"\"");
221    result.check();
222    return result;
223  }
224
225  /**
226   * Parse a path that is part of some other syntax
227   *
228   * @return
229   * @throws PathEngineException
230   * @throws Exception
231   */
232  public ExpressionNode parse(FHIRLexer lexer) throws FHIRLexerException {
233    ExpressionNode result = parseExpression(lexer, true);
234    result.check();
235    return result;
236  }
237
238  /**
239   * check that paths referred to in the ExpressionNode are valid
240   *
241   * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath
242   *
243   * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context
244   *
245   * @param context - the logical type against which this path is applied
246   * @throws DefinitionException
247   * @throws PathEngineException
248   * @if the path is not valid
249   */
250  public TypeDetails check(Object appContext, String resourceType, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
251    // if context is a path that refers to a type, do that conversion now
252        TypeDetails types;
253        if (context == null) {
254          types = null; // this is a special case; the first path reference will have to resolve to something in the context
255        } else if (!context.contains(".")) {
256    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, context);
257          types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
258        } else {
259          String ctxt = context.substring(0, context.indexOf('.'));
260      if (Utilities.isAbsoluteUrl(resourceType)) {
261        ctxt = resourceType.substring(0, resourceType.lastIndexOf("/")+1)+ctxt;
262      }
263          StructureDefinition sd = worker.fetchResource(StructureDefinition.class, ctxt);
264          if (sd == null)
265            throw new PathEngineException("Unknown context "+context);
266          ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
267          if (ed == null)
268            throw new PathEngineException("Unknown context element "+context);
269          if (ed.fixedType != null)
270            types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
271          else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType()))
272            types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+context);
273          else {
274            types = new TypeDetails(CollectionStatus.SINGLETON);
275                for (TypeRefComponent t : ed.getDefinition().getType())
276                  types.addType(t.getCode());
277          }
278        }
279
280    return executeType(new ExecutionTypeContext(appContext, resourceType, context, types), types, expr, true);
281  }
282
283  public TypeDetails check(Object appContext, StructureDefinition sd, String context, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
284    // if context is a path that refers to a type, do that conversion now
285    TypeDetails types;
286    if (!context.contains(".")) {
287      types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl());
288    } else {
289      ElementDefinitionMatch ed = getElementDefinition(sd, context, true);
290      if (ed == null)
291        throw new PathEngineException("Unknown context element "+context);
292      if (ed.fixedType != null)
293        types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType);
294      else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType()))
295        types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()+"#"+context);
296      else {
297        types = new TypeDetails(CollectionStatus.SINGLETON);
298        for (TypeRefComponent t : ed.getDefinition().getType())
299          types.addType(t.getCode());
300      }
301    }
302
303    return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), context, types), types, expr, true);
304  }
305
306  public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
307    // if context is a path that refers to a type, do that conversion now
308    TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
309    return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true);
310  }
311
312  public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
313    return check(appContext, resourceType, context, parse(expr));
314  }
315
316
317  /**
318   * evaluate a path and return the matching elements
319   *
320   * @param base - the object against which the path is being evaluated
321   * @param ExpressionNode - the parsed ExpressionNode statement to use
322   * @return
323   * @throws FHIRException
324   * @
325   */
326        public List<Base> evaluate(Base base, ExpressionNode ExpressionNode) throws FHIRException {
327    List<Base> list = new ArrayList<Base>();
328    if (base != null)
329      list.add(base);
330    log = new StringBuilder();
331    return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base, null, base), list, ExpressionNode, true);
332  }
333
334  /**
335   * evaluate a path and return the matching elements
336   *
337   * @param base - the object against which the path is being evaluated
338   * @param path - the FHIR Path statement to use
339   * @return
340         * @throws FHIRException
341   * @
342   */
343        public List<Base> evaluate(Base base, String path) throws FHIRException {
344    ExpressionNode exp = parse(path);
345    List<Base> list = new ArrayList<Base>();
346    if (base != null)
347      list.add(base);
348    log = new StringBuilder();
349    return execute(new ExecutionContext(null, base.isResource() ? base : null, base, null, base), list, exp, true);
350  }
351
352  /**
353   * evaluate a path and return the matching elements
354   *
355   * @param base - the object against which the path is being evaluated
356   * @param ExpressionNode - the parsed ExpressionNode statement to use
357   * @return
358         * @throws FHIRException
359   * @
360   */
361        public List<Base> evaluate(Object appContext, Resource resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
362    List<Base> list = new ArrayList<Base>();
363    if (base != null)
364      list.add(base);
365    log = new StringBuilder();
366    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
367  }
368
369  /**
370   * evaluate a path and return the matching elements
371   *
372   * @param base - the object against which the path is being evaluated
373   * @param ExpressionNode - the parsed ExpressionNode statement to use
374   * @return
375   * @throws FHIRException
376   * @
377   */
378  public List<Base> evaluate(Object appContext, Base resource, Base base, ExpressionNode ExpressionNode) throws FHIRException {
379    List<Base> list = new ArrayList<Base>();
380    if (base != null)
381      list.add(base);
382    log = new StringBuilder();
383    return execute(new ExecutionContext(appContext, resource, base, null, base), list, ExpressionNode, true);
384  }
385
386  /**
387   * evaluate a path and return the matching elements
388   *
389   * @param base - the object against which the path is being evaluated
390   * @param path - the FHIR Path statement to use
391   * @return
392         * @throws FHIRException
393   * @
394   */
395        public List<Base> evaluate(Object appContext, Resource resource, Base base, String path) throws FHIRException {
396    ExpressionNode exp = parse(path);
397    List<Base> list = new ArrayList<Base>();
398    if (base != null)
399      list.add(base);
400    log = new StringBuilder();
401    return execute(new ExecutionContext(appContext, resource, base, null, base), list, exp, true);
402  }
403
404  /**
405   * evaluate a path and return true or false (e.g. for an invariant)
406   *
407   * @param base - the object against which the path is being evaluated
408   * @param path - the FHIR Path statement to use
409   * @return
410         * @throws FHIRException
411   * @
412   */
413        public boolean evaluateToBoolean(Resource resource, Base base, String path) throws FHIRException {
414    return convertToBoolean(evaluate(null, resource, base, path));
415  }
416
417  /**
418   * evaluate a path and return true or false (e.g. for an invariant)
419   *
420   * @param base - the object against which the path is being evaluated
421   * @return
422   * @throws FHIRException
423   * @
424   */
425  public boolean evaluateToBoolean(Resource resource, Base base, ExpressionNode node) throws FHIRException {
426    return convertToBoolean(evaluate(null, resource, base, node));
427  }
428
429  /**
430   * evaluate a path and return true or false (e.g. for an invariant)
431   *
432   * @param base - the object against which the path is being evaluated
433   * @return
434   * @throws FHIRException
435   * @
436   */
437  public boolean evaluateToBoolean(Object appInfo, Resource resource, Base base, ExpressionNode node) throws FHIRException {
438    return convertToBoolean(evaluate(appInfo, resource, base, node));
439  }
440
441  /**
442   * evaluate a path and return true or false (e.g. for an invariant)
443   *
444   * @param base - the object against which the path is being evaluated
445   * @return
446   * @throws FHIRException
447   * @
448   */
449  public boolean evaluateToBoolean(Base resource, Base base, ExpressionNode node) throws FHIRException {
450    return convertToBoolean(evaluate(null, resource, base, node));
451  }
452
453  /**
454   * evaluate a path and a string containing the outcome (for display)
455   *
456   * @param base - the object against which the path is being evaluated
457   * @param path - the FHIR Path statement to use
458   * @return
459         * @throws FHIRException
460   * @
461   */
462  public String evaluateToString(Base base, String path) throws FHIRException {
463    return convertToString(evaluate(base, path));
464  }
465
466  public String evaluateToString(Object appInfo, Base resource, Base base, ExpressionNode node) throws FHIRException {
467    return convertToString(evaluate(appInfo, resource, base, node));
468  }
469
470  /**
471   * worker routine for converting a set of objects to a string representation
472   *
473   * @param items - result from @evaluate
474   * @return
475   */
476  public String convertToString(List<Base> items) {
477    StringBuilder b = new StringBuilder();
478    boolean first = true;
479    for (Base item : items) {
480      if (first)
481        first = false;
482      else
483        b.append(',');
484
485      b.append(convertToString(item));
486    }
487    return b.toString();
488  }
489
490  private String convertToString(Base item) {
491    if (item.isPrimitive())
492      return item.primitiveValue();
493    else
494      return item.toString();
495  }
496
497  /**
498   * worker routine for converting a set of objects to a boolean representation (for invariants)
499   *
500   * @param items - result from @evaluate
501   * @return
502   */
503  public boolean convertToBoolean(List<Base> items) {
504    if (items == null)
505      return false;
506    else if (items.size() == 1 && items.get(0) instanceof BooleanType)
507      return ((BooleanType) items.get(0)).getValue();
508    else
509      return items.size() > 0;
510  }
511
512
513  private void log(String name, List<Base> contents) {
514    if (hostServices == null || !hostServices.log(name, contents)) {
515      if (log.length() > 0)
516        log.append("; ");
517      log.append(name);
518      log.append(": ");
519      boolean first = true;
520      for (Base b : contents) {
521        if (first)
522          first = false;
523        else
524          log.append(",");
525        log.append(convertToString(b));
526      }
527    }
528  }
529
530  public String forLog() {
531    if (log.length() > 0)
532      return " ("+log.toString()+")";
533    else
534      return "";
535  }
536
537  private ExpressionNode parseExpression(FHIRLexer lexer, boolean proximal) throws FHIRLexerException {
538    ExpressionNode result = new ExpressionNode(lexer.nextId());
539    SourceLocation c = lexer.getCurrentStartLocation();
540    result.setStart(lexer.getCurrentLocation());
541    // special:
542    if (lexer.getCurrent().equals("-")) {
543      lexer.take();
544      lexer.setCurrent("-"+lexer.getCurrent());
545    }
546    if (lexer.getCurrent().equals("+")) {
547      lexer.take();
548      lexer.setCurrent("+"+lexer.getCurrent());
549    }
550    if (lexer.isConstant(false)) {
551      checkConstant(lexer.getCurrent(), lexer);
552      result.setConstant(lexer.take());
553      result.setKind(Kind.Constant);
554      result.setEnd(lexer.getCurrentLocation());
555    } else if ("(".equals(lexer.getCurrent())) {
556      lexer.next();
557      result.setKind(Kind.Group);
558      result.setGroup(parseExpression(lexer, true));
559      if (!")".equals(lexer.getCurrent()))
560        throw lexer.error("Found "+lexer.getCurrent()+" expecting a \")\"");
561      result.setEnd(lexer.getCurrentLocation());
562      lexer.next();
563    } else {
564      if (!lexer.isToken() && !lexer.getCurrent().startsWith("\""))
565        throw lexer.error("Found "+lexer.getCurrent()+" expecting a token name");
566      if (lexer.getCurrent().startsWith("\""))
567        result.setName(lexer.readConstant("Path Name"));
568      else
569        result.setName(lexer.take());
570      result.setEnd(lexer.getCurrentLocation());
571      if (!result.checkName())
572        throw lexer.error("Found "+result.getName()+" expecting a valid token name");
573      if ("(".equals(lexer.getCurrent())) {
574        Function f = Function.fromCode(result.getName());
575        FunctionDetails details = null;
576        if (f == null) {
577          if (hostServices != null)
578            details = hostServices.resolveFunction(result.getName());
579          if (details == null)
580            throw lexer.error("The name "+result.getName()+" is not a valid function name");
581          f = Function.Custom;
582        }
583        result.setKind(Kind.Function);
584        result.setFunction(f);
585        lexer.next();
586        while (!")".equals(lexer.getCurrent())) {
587          result.getParameters().add(parseExpression(lexer, true));
588          if (",".equals(lexer.getCurrent()))
589            lexer.next();
590          else if (!")".equals(lexer.getCurrent()))
591            throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - either a \",\" or a \")\" expected");
592        }
593        result.setEnd(lexer.getCurrentLocation());
594        lexer.next();
595        checkParameters(lexer, c, result, details);
596      } else
597        result.setKind(Kind.Name);
598    }
599    ExpressionNode focus = result;
600    if ("[".equals(lexer.getCurrent())) {
601      lexer.next();
602      ExpressionNode item = new ExpressionNode(lexer.nextId());
603      item.setKind(Kind.Function);
604      item.setFunction(ExpressionNode.Function.Item);
605      item.getParameters().add(parseExpression(lexer, true));
606      if (!lexer.getCurrent().equals("]"))
607        throw lexer.error("The token "+lexer.getCurrent()+" is not expected here - a \"]\" expected");
608      lexer.next();
609      result.setInner(item);
610      focus = item;
611    }
612    if (".".equals(lexer.getCurrent())) {
613      lexer.next();
614      focus.setInner(parseExpression(lexer, false));
615    }
616    result.setProximal(proximal);
617    if (proximal) {
618      while (lexer.isOp()) {
619        focus.setOperation(ExpressionNode.Operation.fromCode(lexer.getCurrent()));
620        focus.setOpStart(lexer.getCurrentStartLocation());
621        focus.setOpEnd(lexer.getCurrentLocation());
622        lexer.next();
623        focus.setOpNext(parseExpression(lexer, false));
624        focus = focus.getOpNext();
625      }
626      result = organisePrecedence(lexer, result);
627    }
628    return result;
629  }
630
631  private ExpressionNode organisePrecedence(FHIRLexer lexer, ExpressionNode node) {
632    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Times, Operation.DivideBy, Operation.Div, Operation.Mod));
633    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Plus, Operation.Minus, Operation.Concatenate));
634    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Union));
635    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.LessThen, Operation.Greater, Operation.LessOrEqual, Operation.GreaterOrEqual));
636    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Is));
637    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
638    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
639    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
640    // last: implies
641    return node;
642  }
643
644  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
645    //    work : boolean;
646    //    focus, node, group : ExpressionNode;
647
648    assert(start.isProximal());
649
650    // is there anything to do?
651    boolean work = false;
652    ExpressionNode focus = start.getOpNext();
653    if (ops.contains(start.getOperation())) {
654      while (focus != null && focus.getOperation() != null) {
655        work = work || !ops.contains(focus.getOperation());
656        focus = focus.getOpNext();
657      }
658    } else {
659      while (focus != null && focus.getOperation() != null) {
660        work = work || ops.contains(focus.getOperation());
661        focus = focus.getOpNext();
662      }
663    }
664    if (!work)
665      return start;
666
667    // entry point: tricky
668    ExpressionNode group;
669    if (ops.contains(start.getOperation())) {
670      group = newGroup(lexer, start);
671      group.setProximal(true);
672      focus = start;
673      start = group;
674    } else {
675      ExpressionNode node = start;
676
677      focus = node.getOpNext();
678      while (!ops.contains(focus.getOperation())) {
679        node = focus;
680        focus = focus.getOpNext();
681      }
682      group = newGroup(lexer, focus);
683      node.setOpNext(group);
684    }
685
686    // now, at this point:
687    //   group is the group we are adding to, it already has a .group property filled out.
688    //   focus points at the group.group
689    do {
690      // run until we find the end of the sequence
691      while (ops.contains(focus.getOperation()))
692        focus = focus.getOpNext();
693      if (focus.getOperation() != null) {
694        group.setOperation(focus.getOperation());
695        group.setOpNext(focus.getOpNext());
696        focus.setOperation(null);
697        focus.setOpNext(null);
698        // now look for another sequence, and start it
699        ExpressionNode node = group;
700        focus = group.getOpNext();
701        if (focus != null) {
702          while (focus != null && !ops.contains(focus.getOperation())) {
703            node = focus;
704            focus = focus.getOpNext();
705          }
706          if (focus != null) { // && (focus.Operation in Ops) - must be true
707            group = newGroup(lexer, focus);
708            node.setOpNext(group);
709          }
710        }
711      }
712    }
713    while (focus != null && focus.getOperation() != null);
714    return start;
715  }
716
717
718  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
719    ExpressionNode result = new ExpressionNode(lexer.nextId());
720    result.setKind(Kind.Group);
721    result.setGroup(next);
722    result.getGroup().setProximal(true);
723    return result;
724  }
725
726  private void checkConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
727    if (s.startsWith("\'") && s.endsWith("\'")) {
728      int i = 1;
729      while (i < s.length()-1) {
730        char ch = s.charAt(i);
731        if (ch == '\\') {
732          switch (ch) {
733          case 't':
734          case 'r':
735          case 'n':
736          case 'f':
737          case '\'':
738          case '\\':
739          case '/':
740            i++;
741            break;
742          case 'u':
743            if (!Utilities.isHex("0x"+s.substring(i, i+4)))
744              throw lexer.error("Improper unicode escape \\u"+s.substring(i, i+4));
745            break;
746          default:
747            throw lexer.error("Unknown character escape \\"+ch);
748          }
749        } else
750          i++;
751      }
752    }
753  }
754
755  //  procedure CheckParamCount(c : integer);
756  //  begin
757  //    if exp.Parameters.Count <> c then
758  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
759  //  end;
760
761  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
762    if (exp.getParameters().size() != count)
763      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString());
764    return true;
765  }
766
767  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
768    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax)
769      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString());
770    return true;
771  }
772
773  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
774    switch (exp.getFunction()) {
775    case Empty: return checkParamCount(lexer, location, exp, 0);
776    case Not: return checkParamCount(lexer, location, exp, 0);
777    case Exists: return checkParamCount(lexer, location, exp, 0);
778    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
779    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
780    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
781    case Distinct: return checkParamCount(lexer, location, exp, 0);
782    case Count: return checkParamCount(lexer, location, exp, 0);
783    case Where: return checkParamCount(lexer, location, exp, 1);
784    case Select: return checkParamCount(lexer, location, exp, 1);
785    case All: return checkParamCount(lexer, location, exp, 0, 1);
786    case Repeat: return checkParamCount(lexer, location, exp, 1);
787    case Item: return checkParamCount(lexer, location, exp, 1);
788    case As: return checkParamCount(lexer, location, exp, 1);
789    case Is: return checkParamCount(lexer, location, exp, 1);
790    case Single: return checkParamCount(lexer, location, exp, 0);
791    case First: return checkParamCount(lexer, location, exp, 0);
792    case Last: return checkParamCount(lexer, location, exp, 0);
793    case Tail: return checkParamCount(lexer, location, exp, 0);
794    case Skip: return checkParamCount(lexer, location, exp, 1);
795    case Take: return checkParamCount(lexer, location, exp, 1);
796    case Iif: return checkParamCount(lexer, location, exp, 2,3);
797    case ToInteger: return checkParamCount(lexer, location, exp, 0);
798    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
799    case ToString: return checkParamCount(lexer, location, exp, 0);
800    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
801    case StartsWith: return checkParamCount(lexer, location, exp, 1);
802    case EndsWith: return checkParamCount(lexer, location, exp, 1);
803    case Matches: return checkParamCount(lexer, location, exp, 1);
804    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
805    case Contains: return checkParamCount(lexer, location, exp, 1);
806    case Replace: return checkParamCount(lexer, location, exp, 2);
807    case Length: return checkParamCount(lexer, location, exp, 0);
808    case Children: return checkParamCount(lexer, location, exp, 0);
809    case Descendants: return checkParamCount(lexer, location, exp, 0);
810    case MemberOf: return checkParamCount(lexer, location, exp, 1);
811    case Trace: return checkParamCount(lexer, location, exp, 1);
812    case Today: return checkParamCount(lexer, location, exp, 0);
813    case Now: return checkParamCount(lexer, location, exp, 0);
814    case Resolve: return checkParamCount(lexer, location, exp, 0);
815    case Extension: return checkParamCount(lexer, location, exp, 1);
816    case HasValue: return checkParamCount(lexer, location, exp, 0);
817    case Alias: return checkParamCount(lexer, location, exp, 1);
818    case AliasAs: return checkParamCount(lexer, location, exp, 1);
819    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
820    }
821    return false;
822  }
823
824        private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
825//    System.out.println("Evaluate {'"+exp.toString()+"'} on "+focus.toString());
826    List<Base> work = new ArrayList<Base>();
827    switch (exp.getKind()) {
828    case Name:
829      if (atEntry && exp.getName().equals("$this"))
830        work.add(context.getThisItem());
831      else
832        for (Base item : focus) {
833          List<Base> outcome = execute(context, item, exp, atEntry);
834          for (Base base : outcome)
835            if (base != null)
836              work.add(base);
837        }
838      break;
839    case Function:
840      List<Base> work2 = evaluateFunction(context, focus, exp);
841      work.addAll(work2);
842      break;
843    case Constant:
844      Base b = processConstant(context, exp.getConstant());
845      if (b != null)
846        work.add(b);
847      break;
848    case Group:
849      work2 = execute(context, focus, exp.getGroup(), atEntry);
850      work.addAll(work2);
851    }
852
853    if (exp.getInner() != null)
854      work = execute(context, work, exp.getInner(), false);
855
856    if (exp.isProximal() && exp.getOperation() != null) {
857      ExpressionNode next = exp.getOpNext();
858      ExpressionNode last = exp;
859      while (next != null) {
860        List<Base> work2 = preOperate(work, last.getOperation());
861        if (work2 != null)
862          work = work2;
863        else if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
864          work2 = executeTypeName(context, focus, next, false);
865          work = operate(work, last.getOperation(), work2);
866        } else {
867          work2 = execute(context, focus, next, true);
868          work = operate(work, last.getOperation(), work2);
869//          System.out.println("Result of {'"+last.toString()+" "+last.getOperation().toCode()+" "+next.toString()+"'}: "+focus.toString());
870        }
871        last = next;
872        next = next.getOpNext();
873      }
874    }
875//    System.out.println("Result of {'"+exp.toString()+"'}: "+work.toString());
876    return work;
877  }
878
879  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
880    List<Base> result = new ArrayList<Base>();
881    result.add(new StringType(next.getName()));
882    return result;
883  }
884
885
886  private List<Base> preOperate(List<Base> left, Operation operation) {
887    switch (operation) {
888    case And:
889      return isBoolean(left, false) ? makeBoolean(false) : null;
890    case Or:
891      return isBoolean(left, true) ? makeBoolean(true) : null;
892    case Implies:
893      return convertToBoolean(left) ? null : makeBoolean(true);
894    default:
895      return null;
896    }
897  }
898
899  private List<Base> makeBoolean(boolean b) {
900    List<Base> res = new ArrayList<Base>();
901    res.add(new BooleanType(b));
902    return res;
903  }
904
905  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
906    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
907  }
908
909  private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
910    TypeDetails result = new TypeDetails(null);
911    switch (exp.getKind()) {
912    case Name:
913      if (atEntry && exp.getName().equals("$this"))
914        result.update(context.getThisItem());
915      else if (atEntry && focus == null)
916        result.update(executeContextType(context, exp.getName()));
917      else {
918        for (String s : focus.getTypes()) {
919          result.update(executeType(s, exp, atEntry));
920        }
921        if (result.hasNoTypes())
922          throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe());
923      }
924      break;
925    case Function:
926      result.update(evaluateFunctionType(context, focus, exp));
927      break;
928    case Constant:
929      result.update(readConstantType(context, exp.getConstant()));
930      break;
931    case Group:
932      result.update(executeType(context, focus, exp.getGroup(), atEntry));
933    }
934    exp.setTypes(result);
935
936    if (exp.getInner() != null) {
937      result = executeType(context, result, exp.getInner(), false);
938    }
939
940    if (exp.isProximal() && exp.getOperation() != null) {
941      ExpressionNode next = exp.getOpNext();
942      ExpressionNode last = exp;
943      while (next != null) {
944        TypeDetails work;
945        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As)
946          work = executeTypeName(context, focus, next, atEntry);
947        else
948          work = executeType(context, focus, next, atEntry);
949        result = operateTypes(result, last.getOperation(), work);
950        last = next;
951        next = next.getOpNext();
952      }
953      exp.setOpTypes(result);
954    }
955    return result;
956  }
957
958  private Base processConstant(ExecutionContext context, String constant) throws PathEngineException {
959    if (constant.equals("true")) {
960      return new BooleanType(true);
961    } else if (constant.equals("false")) {
962      return new BooleanType(false);
963    } else if (constant.equals("{}")) {
964      return null;
965    } else if (Utilities.isInteger(constant)) {
966      return new IntegerType(constant);
967    } else if (Utilities.isDecimal(constant, false)) {
968      return new DecimalType(constant);
969    } else if (constant.startsWith("\'")) {
970      return new StringType(processConstantString(constant));
971    } else if (constant.startsWith("%")) {
972      return resolveConstant(context, constant);
973    } else if (constant.startsWith("@")) {
974      return processDateConstant(context.getAppInfo(), constant.substring(1));
975    } else {
976      return new StringType(constant);
977    }
978  }
979
980  private Base processDateConstant(Object appInfo, String value) throws PathEngineException {
981    if (value.startsWith("T"))
982      return new TimeType(value.substring(1));
983    String v = value;
984    if (v.length() > 10) {
985      int i = v.substring(10).indexOf("-");
986      if (i == -1)
987        i = v.substring(10).indexOf("+");
988      if (i == -1)
989        i = v.substring(10).indexOf("Z");
990      v = i == -1 ? value : v.substring(0,  10+i);
991    }
992    if (v.length() > 10)
993      return new DateTimeType(value);
994    else
995      return new DateType(value);
996  }
997
998
999  private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException {
1000    if (s.equals("%sct"))
1001      return new StringType("http://snomed.info/sct");
1002    else if (s.equals("%loinc"))
1003      return new StringType("http://loinc.org");
1004    else if (s.equals("%ucum"))
1005      return new StringType("http://unitsofmeasure.org");
1006    else if (s.equals("%resource")) {
1007      if (context.getResource() == null)
1008        throw new PathEngineException("Cannot use %resource in this context");
1009      return context.getResource();
1010    } else if (s.equals("%context")) {
1011      return context.getContext();
1012    } else if (s.equals("%us-zip"))
1013      return new StringType("[0-9]{5}(-[0-9]{4}){0,1}");
1014    else if (s.startsWith("%\"vs-"))
1015      return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"");
1016    else if (s.startsWith("%\"cs-"))
1017      return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"");
1018    else if (s.startsWith("%\"ext-"))
1019      return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1));
1020    else if (hostServices == null)
1021      throw new PathEngineException("Unknown fixed constant '"+s+"'");
1022    else
1023      return hostServices.resolveConstant(context.getAppInfo(), s.substring(1));
1024  }
1025
1026
1027  private String processConstantString(String s) throws PathEngineException {
1028    StringBuilder b = new StringBuilder();
1029    int i = 1;
1030    while (i < s.length()-1) {
1031      char ch = s.charAt(i);
1032      if (ch == '\\') {
1033        i++;
1034        switch (s.charAt(i)) {
1035        case 't':
1036          b.append('\t');
1037          break;
1038        case 'r':
1039          b.append('\r');
1040          break;
1041        case 'n':
1042          b.append('\n');
1043          break;
1044        case 'f':
1045          b.append('\f');
1046          break;
1047        case '\'':
1048          b.append('\'');
1049          break;
1050        case '\\':
1051          b.append('\\');
1052          break;
1053        case '/':
1054          b.append('/');
1055          break;
1056        case 'u':
1057          i++;
1058          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1059          b.append((char) uc);
1060          i = i + 3;
1061          break;
1062        default:
1063          throw new PathEngineException("Unknown character escape \\"+s.charAt(i));
1064        }
1065        i++;
1066      } else {
1067        b.append(ch);
1068        i++;
1069      }
1070    }
1071    return b.toString();
1072  }
1073
1074
1075  private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException {
1076    switch (operation) {
1077    case Equals: return opEquals(left, right);
1078    case Equivalent: return opEquivalent(left, right);
1079    case NotEquals: return opNotEquals(left, right);
1080    case NotEquivalent: return opNotEquivalent(left, right);
1081    case LessThen: return opLessThen(left, right);
1082    case Greater: return opGreater(left, right);
1083    case LessOrEqual: return opLessOrEqual(left, right);
1084    case GreaterOrEqual: return opGreaterOrEqual(left, right);
1085    case Union: return opUnion(left, right);
1086    case In: return opIn(left, right);
1087    case Contains: return opContains(left, right);
1088    case Or:  return opOr(left, right);
1089    case And:  return opAnd(left, right);
1090    case Xor: return opXor(left, right);
1091    case Implies: return opImplies(left, right);
1092    case Plus: return opPlus(left, right);
1093    case Times: return opTimes(left, right);
1094    case Minus: return opMinus(left, right);
1095    case Concatenate: return opConcatenate(left, right);
1096    case DivideBy: return opDivideBy(left, right);
1097    case Div: return opDiv(left, right);
1098    case Mod: return opMod(left, right);
1099    case Is: return opIs(left, right);
1100    case As: return opAs(left, right);
1101    default:
1102      throw new Error("Not Done Yet: "+operation.toCode());
1103    }
1104  }
1105
1106  private List<Base> opAs(List<Base> left, List<Base> right) {
1107    List<Base> result = new ArrayList<Base>();
1108    if (left.size() != 1 || right.size() != 1)
1109      return result;
1110    else {
1111      String tn = convertToString(right);
1112      if (tn.equals(left.get(0).fhirType()))
1113        result.add(left.get(0));
1114    }
1115    return result;
1116  }
1117
1118
1119  private List<Base> opIs(List<Base> left, List<Base> right) {
1120    List<Base> result = new ArrayList<Base>();
1121    if (left.size() != 1 || right.size() != 1)
1122      result.add(new BooleanType(false));
1123    else {
1124      String tn = convertToString(right);
1125      result.add(new BooleanType(left.get(0).hasType(tn)));
1126    }
1127    return result;
1128  }
1129
1130
1131  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) {
1132    switch (operation) {
1133    case Equals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1134    case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1135    case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1136    case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1137    case LessThen: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1138    case Greater: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1139    case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1140    case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1141    case Is: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1142    case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
1143    case Union: return left.union(right);
1144    case Or: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1145    case And: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1146    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1147    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1148    case Times:
1149      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
1150      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1151        result.addType("integer");
1152      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1153        result.addType("decimal");
1154      return result;
1155    case DivideBy:
1156      result = new TypeDetails(CollectionStatus.SINGLETON);
1157      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1158        result.addType("decimal");
1159      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1160        result.addType("decimal");
1161      return result;
1162    case Concatenate:
1163      result = new TypeDetails(CollectionStatus.SINGLETON, "");
1164      return result;
1165    case Plus:
1166      result = new TypeDetails(CollectionStatus.SINGLETON);
1167      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1168        result.addType("integer");
1169      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1170        result.addType("decimal");
1171      else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri"))
1172        result.addType("string");
1173      return result;
1174    case Minus:
1175      result = new TypeDetails(CollectionStatus.SINGLETON);
1176      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1177        result.addType("integer");
1178      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1179        result.addType("decimal");
1180      return result;
1181    case Div:
1182    case Mod:
1183      result = new TypeDetails(CollectionStatus.SINGLETON);
1184      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1185        result.addType("integer");
1186      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1187        result.addType("decimal");
1188      return result;
1189    case In: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1190    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1191    default:
1192      return null;
1193    }
1194  }
1195
1196
1197  private List<Base> opEquals(List<Base> left, List<Base> right) {
1198    if (left.size() != right.size())
1199      return makeBoolean(false);
1200
1201    boolean res = true;
1202    for (int i = 0; i < left.size(); i++) {
1203      if (!doEquals(left.get(i), right.get(i))) {
1204        res = false;
1205        break;
1206      }
1207    }
1208    return makeBoolean(res);
1209  }
1210
1211  private List<Base> opNotEquals(List<Base> left, List<Base> right) {
1212    if (left.size() != right.size())
1213      return makeBoolean(true);
1214
1215    boolean res = true;
1216    for (int i = 0; i < left.size(); i++) {
1217      if (!doEquals(left.get(i), right.get(i))) {
1218        res = false;
1219        break;
1220      }
1221    }
1222    return makeBoolean(!res);
1223  }
1224
1225  private boolean doEquals(Base left, Base right) {
1226    if (left.isPrimitive() && right.isPrimitive())
1227                        return Base.equals(left.primitiveValue(), right.primitiveValue());
1228    else
1229      return Base.compareDeep(left, right, false);
1230  }
1231
1232  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
1233    if (left.hasType("integer") && right.hasType("integer"))
1234      return doEquals(left, right);
1235    if (left.hasType("boolean") && right.hasType("boolean"))
1236      return doEquals(left, right);
1237    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt"))
1238      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1239    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant"))
1240      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1241    if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri"))
1242      return Utilities.equivalent(convertToString(left), convertToString(right));
1243
1244    throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()));
1245  }
1246
1247  private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1248    if (left.size() != right.size())
1249      return makeBoolean(false);
1250
1251    boolean res = true;
1252    for (int i = 0; i < left.size(); i++) {
1253      boolean found = false;
1254      for (int j = 0; j < right.size(); j++) {
1255        if (doEquivalent(left.get(i), right.get(j))) {
1256          found = true;
1257          break;
1258        }
1259      }
1260      if (!found) {
1261        res = false;
1262        break;
1263      }
1264    }
1265    return makeBoolean(res);
1266  }
1267
1268  private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1269    if (left.size() != right.size())
1270      return makeBoolean(true);
1271
1272    boolean res = true;
1273    for (int i = 0; i < left.size(); i++) {
1274      boolean found = false;
1275      for (int j = 0; j < right.size(); j++) {
1276        if (doEquivalent(left.get(i), right.get(j))) {
1277          found = true;
1278          break;
1279        }
1280      }
1281      if (!found) {
1282        res = false;
1283        break;
1284      }
1285    }
1286    return makeBoolean(!res);
1287  }
1288
1289        private List<Base> opLessThen(List<Base> left, List<Base> right) throws FHIRException {
1290    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1291      Base l = left.get(0);
1292      Base r = right.get(0);
1293      if (l.hasType("string") && r.hasType("string"))
1294        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1295      else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal")))
1296        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
1297      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1298        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1299      else if ((l.hasType("time")) && (r.hasType("time")))
1300        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1301    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1302      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1303      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1304      if (Base.compareDeep(lUnit, rUnit, true)) {
1305        return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1306      } else {
1307                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1308      }
1309    }
1310    return new ArrayList<Base>();
1311  }
1312
1313        private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException {
1314    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1315      Base l = left.get(0);
1316      Base r = right.get(0);
1317      if (l.hasType("string") && r.hasType("string"))
1318        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1319      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1320        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
1321      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1322        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1323      else if ((l.hasType("time")) && (r.hasType("time")))
1324        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1325    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1326      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1327      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1328      if (Base.compareDeep(lUnit, rUnit, true)) {
1329        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1330      } else {
1331                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1332      }
1333    }
1334    return new ArrayList<Base>();
1335  }
1336
1337        private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1338    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1339      Base l = left.get(0);
1340      Base r = right.get(0);
1341      if (l.hasType("string") && r.hasType("string"))
1342        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1343      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1344        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
1345      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1346        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1347      else if ((l.hasType("time")) && (r.hasType("time")))
1348        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1349    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1350      List<Base> lUnits = left.get(0).listChildrenByName("unit");
1351      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
1352      List<Base> rUnits = right.get(0).listChildrenByName("unit");
1353      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
1354      if ((lunit == null && runit == null) || lunit.equals(runit)) {
1355        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1356      } else {
1357                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1358      }
1359    }
1360    return new ArrayList<Base>();
1361  }
1362
1363        private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1364    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1365      Base l = left.get(0);
1366      Base r = right.get(0);
1367      if (l.hasType("string") && r.hasType("string"))
1368        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1369      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1370        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
1371      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1372        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1373      else if ((l.hasType("time")) && (r.hasType("time")))
1374        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1375    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1376      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1377      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1378      if (Base.compareDeep(lUnit, rUnit, true)) {
1379        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1380      } else {
1381                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1382      }
1383    }
1384    return new ArrayList<Base>();
1385  }
1386
1387  private List<Base> opIn(List<Base> left, List<Base> right) {
1388    boolean ans = true;
1389    for (Base l : left) {
1390      boolean f = false;
1391      for (Base r : right)
1392        if (doEquals(l, r)) {
1393          f = true;
1394          break;
1395        }
1396      if (!f) {
1397        ans = false;
1398        break;
1399      }
1400    }
1401    return makeBoolean(ans);
1402  }
1403
1404  private List<Base> opContains(List<Base> left, List<Base> right) {
1405    boolean ans = true;
1406    for (Base r : right) {
1407      boolean f = false;
1408      for (Base l : left)
1409        if (doEquals(l, r)) {
1410          f = true;
1411          break;
1412        }
1413      if (!f) {
1414        ans = false;
1415        break;
1416      }
1417    }
1418    return makeBoolean(ans);
1419  }
1420
1421  private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException {
1422    if (left.size() == 0)
1423      throw new PathEngineException("Error performing +: left operand has no value");
1424    if (left.size() > 1)
1425      throw new PathEngineException("Error performing +: left operand has more than one value");
1426    if (!left.get(0).isPrimitive())
1427      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1428    if (right.size() == 0)
1429      throw new PathEngineException("Error performing +: right operand has no value");
1430    if (right.size() > 1)
1431      throw new PathEngineException("Error performing +: right operand has more than one value");
1432    if (!right.get(0).isPrimitive())
1433      throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType()));
1434
1435    List<Base> result = new ArrayList<Base>();
1436    Base l = left.get(0);
1437    Base r = right.get(0);
1438    if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri"))
1439      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
1440    else if (l.hasType("integer") && r.hasType("integer"))
1441      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
1442    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1443      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
1444    else
1445      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()));
1446    return result;
1447  }
1448
1449  private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException {
1450    if (left.size() == 0)
1451      throw new PathEngineException("Error performing *: left operand has no value");
1452    if (left.size() > 1)
1453      throw new PathEngineException("Error performing *: left operand has more than one value");
1454    if (!left.get(0).isPrimitive())
1455      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1456    if (right.size() == 0)
1457      throw new PathEngineException("Error performing *: right operand has no value");
1458    if (right.size() > 1)
1459      throw new PathEngineException("Error performing *: right operand has more than one value");
1460    if (!right.get(0).isPrimitive())
1461      throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType()));
1462
1463    List<Base> result = new ArrayList<Base>();
1464    Base l = left.get(0);
1465    Base r = right.get(0);
1466
1467    if (l.hasType("integer") && r.hasType("integer"))
1468      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
1469    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1470      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
1471    else
1472      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()));
1473    return result;
1474  }
1475
1476  private List<Base> opConcatenate(List<Base> left, List<Base> right) {
1477    List<Base> result = new ArrayList<Base>();
1478    result.add(new StringType(convertToString(left) + convertToString((right))));
1479    return result;
1480  }
1481
1482  private List<Base> opUnion(List<Base> left, List<Base> right) {
1483    List<Base> result = new ArrayList<Base>();
1484    for (Base item : left) {
1485      if (!doContains(result, item))
1486        result.add(item);
1487    }
1488    for (Base item : right) {
1489      if (!doContains(result, item))
1490        result.add(item);
1491    }
1492    return result;
1493  }
1494
1495  private boolean doContains(List<Base> list, Base item) {
1496    for (Base test : list)
1497      if (doEquals(test, item))
1498        return true;
1499    return false;
1500  }
1501
1502
1503  private List<Base> opAnd(List<Base> left, List<Base> right) {
1504    if (left.isEmpty() && right.isEmpty())
1505      return new ArrayList<Base>();
1506    else if (isBoolean(left, false) || isBoolean(right, false))
1507      return makeBoolean(false);
1508    else if (left.isEmpty() || right.isEmpty())
1509      return new ArrayList<Base>();
1510    else if (convertToBoolean(left) && convertToBoolean(right))
1511      return makeBoolean(true);
1512    else
1513      return makeBoolean(false);
1514  }
1515
1516  private boolean isBoolean(List<Base> list, boolean b) {
1517    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
1518  }
1519
1520  private List<Base> opOr(List<Base> left, List<Base> right) {
1521    if (left.isEmpty() && right.isEmpty())
1522      return new ArrayList<Base>();
1523    else if (convertToBoolean(left) || convertToBoolean(right))
1524      return makeBoolean(true);
1525    else if (left.isEmpty() || right.isEmpty())
1526      return new ArrayList<Base>();
1527    else
1528      return makeBoolean(false);
1529  }
1530
1531  private List<Base> opXor(List<Base> left, List<Base> right) {
1532    if (left.isEmpty() || right.isEmpty())
1533      return new ArrayList<Base>();
1534    else
1535      return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right));
1536  }
1537
1538  private List<Base> opImplies(List<Base> left, List<Base> right) {
1539    if (!convertToBoolean(left))
1540      return makeBoolean(true);
1541    else if (right.size() == 0)
1542      return new ArrayList<Base>();
1543    else
1544      return makeBoolean(convertToBoolean(right));
1545  }
1546
1547
1548  private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException {
1549    if (left.size() == 0)
1550      throw new PathEngineException("Error performing -: left operand has no value");
1551    if (left.size() > 1)
1552      throw new PathEngineException("Error performing -: left operand has more than one value");
1553    if (!left.get(0).isPrimitive())
1554      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1555    if (right.size() == 0)
1556      throw new PathEngineException("Error performing -: right operand has no value");
1557    if (right.size() > 1)
1558      throw new PathEngineException("Error performing -: right operand has more than one value");
1559    if (!right.get(0).isPrimitive())
1560      throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType()));
1561
1562    List<Base> result = new ArrayList<Base>();
1563    Base l = left.get(0);
1564    Base r = right.get(0);
1565
1566    if (l.hasType("integer") && r.hasType("integer"))
1567      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
1568    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1569      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
1570    else
1571      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()));
1572    return result;
1573  }
1574
1575  private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException {
1576    if (left.size() == 0)
1577      throw new PathEngineException("Error performing /: left operand has no value");
1578    if (left.size() > 1)
1579      throw new PathEngineException("Error performing /: left operand has more than one value");
1580    if (!left.get(0).isPrimitive())
1581      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1582    if (right.size() == 0)
1583      throw new PathEngineException("Error performing /: right operand has no value");
1584    if (right.size() > 1)
1585      throw new PathEngineException("Error performing /: right operand has more than one value");
1586    if (!right.get(0).isPrimitive())
1587      throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType()));
1588
1589    List<Base> result = new ArrayList<Base>();
1590    Base l = left.get(0);
1591    Base r = right.get(0);
1592
1593    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
1594      Decimal d1;
1595      try {
1596        d1 = new Decimal(l.primitiveValue());
1597        Decimal d2 = new Decimal(r.primitiveValue());
1598        result.add(new DecimalType(d1.divide(d2).asDecimal()));
1599      } catch (UcumException e) {
1600        throw new PathEngineException(e);
1601      }
1602    }
1603    else
1604      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()));
1605    return result;
1606  }
1607
1608  private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException {
1609    if (left.size() == 0)
1610      throw new PathEngineException("Error performing div: left operand has no value");
1611    if (left.size() > 1)
1612      throw new PathEngineException("Error performing div: left operand has more than one value");
1613    if (!left.get(0).isPrimitive())
1614      throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType()));
1615    if (right.size() == 0)
1616      throw new PathEngineException("Error performing div: right operand has no value");
1617    if (right.size() > 1)
1618      throw new PathEngineException("Error performing div: right operand has more than one value");
1619    if (!right.get(0).isPrimitive())
1620      throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType()));
1621
1622    List<Base> result = new ArrayList<Base>();
1623    Base l = left.get(0);
1624    Base r = right.get(0);
1625
1626    if (l.hasType("integer") && r.hasType("integer"))
1627      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue())));
1628    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
1629      Decimal d1;
1630      try {
1631        d1 = new Decimal(l.primitiveValue());
1632        Decimal d2 = new Decimal(r.primitiveValue());
1633        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
1634      } catch (UcumException e) {
1635        throw new PathEngineException(e);
1636      }
1637    }
1638    else
1639      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()));
1640    return result;
1641  }
1642
1643  private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException {
1644    if (left.size() == 0)
1645      throw new PathEngineException("Error performing mod: left operand has no value");
1646    if (left.size() > 1)
1647      throw new PathEngineException("Error performing mod: left operand has more than one value");
1648    if (!left.get(0).isPrimitive())
1649      throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType()));
1650    if (right.size() == 0)
1651      throw new PathEngineException("Error performing mod: right operand has no value");
1652    if (right.size() > 1)
1653      throw new PathEngineException("Error performing mod: right operand has more than one value");
1654    if (!right.get(0).isPrimitive())
1655      throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType()));
1656
1657    List<Base> result = new ArrayList<Base>();
1658    Base l = left.get(0);
1659    Base r = right.get(0);
1660
1661    if (l.hasType("integer") && r.hasType("integer"))
1662      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue())));
1663    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
1664      Decimal d1;
1665      try {
1666        d1 = new Decimal(l.primitiveValue());
1667        Decimal d2 = new Decimal(r.primitiveValue());
1668        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
1669      } catch (UcumException e) {
1670        throw new PathEngineException(e);
1671      }
1672    }
1673    else
1674      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()));
1675    return result;
1676  }
1677
1678
1679  private TypeDetails readConstantType(ExecutionTypeContext context, String constant) throws PathEngineException {
1680    if (constant.equals("true"))
1681      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1682    else if (constant.equals("false"))
1683      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1684    else if (Utilities.isInteger(constant))
1685      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1686    else if (Utilities.isDecimal(constant, false))
1687      return new TypeDetails(CollectionStatus.SINGLETON, "decimal");
1688    else if (constant.startsWith("%"))
1689      return resolveConstantType(context, constant);
1690    else
1691      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1692  }
1693
1694  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException {
1695    if (s.equals("%sct"))
1696      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1697    else if (s.equals("%loinc"))
1698      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1699    else if (s.equals("%ucum"))
1700      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1701    else if (s.equals("%resource")) {
1702      if (context.getResource() == null)
1703        throw new PathEngineException("%resource cannot be used in this context");
1704      return new TypeDetails(CollectionStatus.SINGLETON, context.getResource());
1705    } else if (s.equals("%context")) {
1706      return new TypeDetails(CollectionStatus.SINGLETON, context.getContext());
1707    } else if (s.equals("%map-codes"))
1708      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1709    else if (s.equals("%us-zip"))
1710      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1711    else if (s.startsWith("%\"vs-"))
1712      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1713    else if (s.startsWith("%\"cs-"))
1714      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1715    else if (s.startsWith("%\"ext-"))
1716      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1717    else if (hostServices == null)
1718      throw new PathEngineException("Unknown fixed constant type for '"+s+"'");
1719    else
1720      return hostServices.resolveConstantType(context.getAppInfo(), s);
1721  }
1722
1723        private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
1724    List<Base> result = new ArrayList<Base>();
1725    if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
1726      if (item.isResource() && item.fhirType().equals(exp.getName()))
1727        result.add(item);
1728    } else
1729      getChildrenByName(item, exp.getName(), result);
1730    if (result.size() == 0 && atEntry && context.getAppInfo() != null) {
1731      Base temp = hostServices.resolveConstant(context.getAppInfo(), exp.getName());
1732      if (temp != null) {
1733        result.add(temp);
1734      }
1735    }
1736    return result;
1737  }
1738
1739  private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException {
1740    if (hostServices == null)
1741      throw new PathEngineException("Unable to resolve context reference since no host services are provided");
1742    return hostServices.resolveConstantType(context.getAppInfo(), name);
1743  }
1744
1745  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1746    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up
1747      return new TypeDetails(CollectionStatus.SINGLETON, type);
1748    TypeDetails result = new TypeDetails(null);
1749    getChildTypesByName(type, exp.getName(), result);
1750    return result;
1751  }
1752
1753
1754  private String hashTail(String type) {
1755    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
1756  }
1757
1758
1759  @SuppressWarnings("unchecked")
1760  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
1761    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
1762    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As)
1763      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, "string"));
1764    else
1765      for (ExpressionNode expr : exp.getParameters()) {
1766        if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat)
1767          paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
1768        else
1769          paramTypes.add(executeType(context, focus, expr, true));
1770      }
1771    switch (exp.getFunction()) {
1772    case Empty :
1773      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1774    case Not :
1775      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1776    case Exists :
1777      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1778    case SubsetOf : {
1779      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus);
1780      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1781    }
1782    case SupersetOf : {
1783      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus);
1784      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1785    }
1786    case IsDistinct :
1787      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1788    case Distinct :
1789      return focus;
1790    case Count :
1791      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1792    case Where :
1793      return focus;
1794    case Select :
1795      return anything(focus.getCollectionStatus());
1796    case All :
1797      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1798    case Repeat :
1799      return anything(focus.getCollectionStatus());
1800    case Item : {
1801      checkOrdered(focus, "item");
1802      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1803      return focus;
1804    }
1805    case As : {
1806      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1807      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
1808    }
1809    case Is : {
1810      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1811      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1812    }
1813    case Single :
1814      return focus.toSingleton();
1815    case First : {
1816      checkOrdered(focus, "first");
1817      return focus.toSingleton();
1818    }
1819    case Last : {
1820      checkOrdered(focus, "last");
1821      return focus.toSingleton();
1822    }
1823    case Tail : {
1824      checkOrdered(focus, "tail");
1825      return focus;
1826    }
1827    case Skip : {
1828      checkOrdered(focus, "skip");
1829      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1830      return focus;
1831    }
1832    case Take : {
1833      checkOrdered(focus, "take");
1834      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1835      return focus;
1836    }
1837    case Iif : {
1838      TypeDetails types = new TypeDetails(null);
1839      types.update(paramTypes.get(0));
1840      if (paramTypes.size() > 1)
1841        types.update(paramTypes.get(1));
1842      return types;
1843    }
1844    case ToInteger : {
1845      checkContextPrimitive(focus, "toInteger");
1846      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1847    }
1848    case ToDecimal : {
1849      checkContextPrimitive(focus, "toDecimal");
1850      return new TypeDetails(CollectionStatus.SINGLETON, "decimal");
1851    }
1852    case ToString : {
1853      checkContextPrimitive(focus, "toString");
1854      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1855    }
1856    case Substring : {
1857      checkContextString(focus, "subString");
1858      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"), new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1859      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1860    }
1861    case StartsWith : {
1862      checkContextString(focus, "startsWith");
1863      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1864      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1865    }
1866    case EndsWith : {
1867      checkContextString(focus, "endsWith");
1868      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1869      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1870    }
1871    case Matches : {
1872      checkContextString(focus, "matches");
1873      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1874      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1875    }
1876    case ReplaceMatches : {
1877      checkContextString(focus, "replaceMatches");
1878      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string"));
1879      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1880    }
1881    case Contains : {
1882      checkContextString(focus, "contains");
1883      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1884      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1885    }
1886    case Replace : {
1887      checkContextString(focus, "replace");
1888      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string"));
1889      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1890    }
1891    case Length : {
1892      checkContextPrimitive(focus, "length");
1893      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1894    }
1895    case Children :
1896      return childTypes(focus, "*");
1897    case Descendants :
1898      return childTypes(focus, "**");
1899    case MemberOf : {
1900      checkContextCoded(focus, "memberOf");
1901      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1902      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1903    }
1904    case Trace : {
1905      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1906      return focus;
1907    }
1908    case Today :
1909      return new TypeDetails(CollectionStatus.SINGLETON, "date");
1910    case Now :
1911      return new TypeDetails(CollectionStatus.SINGLETON, "dateTime");
1912    case Resolve : {
1913      checkContextReference(focus, "resolve");
1914      return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource");
1915    }
1916    case Extension : {
1917      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1918      return new TypeDetails(CollectionStatus.SINGLETON, "Extension");
1919    }
1920    case HasValue :
1921      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1922    case Alias :
1923      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1924      return anything(CollectionStatus.SINGLETON);
1925    case AliasAs :
1926      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1927      return focus;
1928    case Custom : {
1929      return hostServices.checkFunction(context.getAppInfo(), exp.getName(), paramTypes);
1930    }
1931    default:
1932      break;
1933    }
1934    throw new Error("not Implemented yet");
1935  }
1936
1937
1938  private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
1939    int i = 0;
1940    for (TypeDetails pt : typeSet) {
1941      if (i == paramTypes.size())
1942        return;
1943      TypeDetails actual = paramTypes.get(i);
1944      i++;
1945      for (String a : actual.getTypes()) {
1946        if (!pt.hasType(worker, a))
1947          throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString());
1948      }
1949    }
1950  }
1951
1952  private void checkOrdered(TypeDetails focus, String name) throws PathEngineException {
1953    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED)
1954      throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections");
1955  }
1956
1957  private void checkContextReference(TypeDetails focus, String name) throws PathEngineException {
1958    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference"))
1959      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, Reference");
1960  }
1961
1962
1963  private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException {
1964    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept"))
1965      throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept");
1966  }
1967
1968
1969  private void checkContextString(TypeDetails focus, String name) throws PathEngineException {
1970    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "id"))
1971      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe());
1972  }
1973
1974
1975  private void checkContextPrimitive(TypeDetails focus, String name) throws PathEngineException {
1976    if (!focus.hasType(primitiveTypes))
1977      throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString());
1978  }
1979
1980
1981  private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException {
1982    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
1983    for (String f : focus.getTypes())
1984      getChildTypesByName(f, mask, result);
1985    return result;
1986  }
1987
1988  private TypeDetails anything(CollectionStatus status) {
1989    return new TypeDetails(status, allTypes.keySet());
1990  }
1991
1992  //    private boolean isPrimitiveType(String s) {
1993  //            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");
1994  //    }
1995
1996        private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
1997    switch (exp.getFunction()) {
1998    case Empty : return funcEmpty(context, focus, exp);
1999    case Not : return funcNot(context, focus, exp);
2000    case Exists : return funcExists(context, focus, exp);
2001    case SubsetOf : return funcSubsetOf(context, focus, exp);
2002    case SupersetOf : return funcSupersetOf(context, focus, exp);
2003    case IsDistinct : return funcIsDistinct(context, focus, exp);
2004    case Distinct : return funcDistinct(context, focus, exp);
2005    case Count : return funcCount(context, focus, exp);
2006    case Where : return funcWhere(context, focus, exp);
2007    case Select : return funcSelect(context, focus, exp);
2008    case All : return funcAll(context, focus, exp);
2009    case Repeat : return funcRepeat(context, focus, exp);
2010    case Item : return funcItem(context, focus, exp);
2011    case As : return funcAs(context, focus, exp);
2012    case Is : return funcIs(context, focus, exp);
2013    case Single : return funcSingle(context, focus, exp);
2014    case First : return funcFirst(context, focus, exp);
2015    case Last : return funcLast(context, focus, exp);
2016    case Tail : return funcTail(context, focus, exp);
2017    case Skip : return funcSkip(context, focus, exp);
2018    case Take : return funcTake(context, focus, exp);
2019    case Iif : return funcIif(context, focus, exp);
2020    case ToInteger : return funcToInteger(context, focus, exp);
2021    case ToDecimal : return funcToDecimal(context, focus, exp);
2022    case ToString : return funcToString(context, focus, exp);
2023    case Substring : return funcSubstring(context, focus, exp);
2024    case StartsWith : return funcStartsWith(context, focus, exp);
2025    case EndsWith : return funcEndsWith(context, focus, exp);
2026    case Matches : return funcMatches(context, focus, exp);
2027    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
2028    case Contains : return funcContains(context, focus, exp);
2029    case Replace : return funcReplace(context, focus, exp);
2030    case Length : return funcLength(context, focus, exp);
2031    case Children : return funcChildren(context, focus, exp);
2032    case Descendants : return funcDescendants(context, focus, exp);
2033    case MemberOf : return funcMemberOf(context, focus, exp);
2034    case Trace : return funcTrace(context, focus, exp);
2035    case Today : return funcToday(context, focus, exp);
2036    case Now : return funcNow(context, focus, exp);
2037    case Resolve : return funcResolve(context, focus, exp);
2038    case Extension : return funcExtension(context, focus, exp);
2039    case HasValue : return funcHasValue(context, focus, exp);
2040    case AliasAs : return funcAliasAs(context, focus, exp);
2041    case Alias : return funcAlias(context, focus, exp);
2042    case Custom: {
2043      List<List<Base>> params = new ArrayList<List<Base>>();
2044      for (ExpressionNode p : exp.getParameters())
2045        params.add(execute(context, focus, p, true));
2046      return hostServices.executeFunction(context.getAppInfo(), exp.getName(), params);
2047    }
2048    default:
2049      throw new Error("not Implemented yet");
2050    }
2051  }
2052
2053        private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2054    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2055    String name = nl.get(0).primitiveValue();
2056    context.addAlias(name, focus);
2057    return focus;
2058  }
2059
2060  private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2061    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2062    String name = nl.get(0).primitiveValue();
2063    List<Base> res = new ArrayList<Base>();
2064    Base b = context.getAlias(name);
2065    if (b != null)
2066      res.add(b);
2067    return res;
2068
2069  }
2070
2071  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2072    if (exp.getParameters().size() == 1) {
2073      List<Base> result = new ArrayList<Base>();
2074      List<Base> pc = new ArrayList<Base>();
2075      boolean all = true;
2076      for (Base item : focus) {
2077        pc.clear();
2078        pc.add(item);
2079        if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) {
2080          all = false;
2081          break;
2082        }
2083      }
2084      result.add(new BooleanType(all));
2085      return result;
2086    } else {// (exp.getParameters().size() == 0) {
2087      List<Base> result = new ArrayList<Base>();
2088      boolean all = true;
2089      for (Base item : focus) {
2090        boolean v = false;
2091        if (item instanceof BooleanType) {
2092          v = ((BooleanType) item).booleanValue();
2093        } else
2094          v = item != null;
2095        if (!v) {
2096          all = false;
2097          break;
2098        }
2099      }
2100      result.add(new BooleanType(all));
2101      return result;
2102    }
2103  }
2104
2105
2106  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
2107    return new ExecutionContext(context.getAppInfo(), context.getResource(), context.getContext(), context.getAliases(), newThis);
2108  }
2109
2110  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
2111    return new ExecutionTypeContext(context.getAppInfo(), context.getResource(), context.getContext(), newThis);
2112  }
2113
2114
2115  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2116    List<Base> result = new ArrayList<Base>();
2117    result.add(DateTimeType.now());
2118    return result;
2119  }
2120
2121
2122  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2123    List<Base> result = new ArrayList<Base>();
2124    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
2125    return result;
2126  }
2127
2128
2129  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2130    throw new Error("not Implemented yet");
2131  }
2132
2133
2134  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2135    List<Base> result = new ArrayList<Base>();
2136    List<Base> current = new ArrayList<Base>();
2137    current.addAll(focus);
2138    List<Base> added = new ArrayList<Base>();
2139    boolean more = true;
2140    while (more) {
2141      added.clear();
2142      for (Base item : current) {
2143        getChildrenByName(item, "*", added);
2144      }
2145      more = !added.isEmpty();
2146      result.addAll(added);
2147      current.clear();
2148      current.addAll(added);
2149    }
2150    return result;
2151  }
2152
2153
2154  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2155    List<Base> result = new ArrayList<Base>();
2156    for (Base b : focus)
2157      getChildrenByName(b, "*", result);
2158    return result;
2159  }
2160
2161
2162  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException {
2163    List<Base> result = new ArrayList<Base>();
2164
2165    if (focus.size() == 1) {
2166      String f = convertToString(focus.get(0));
2167
2168      if (!Utilities.noString(f)) {
2169
2170        if (exp.getParameters().size() == 2) {
2171
2172          String t = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2173          String r = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2174
2175          String n = f.replace(t, r);
2176          result.add(new StringType(n));
2177        }
2178        else {
2179          throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size()));
2180        }
2181      }
2182      else {
2183        throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item"));
2184      }
2185    }
2186    else {
2187      throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size()));
2188    }
2189    return result;
2190  }
2191
2192
2193  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2194    List<Base> result = new ArrayList<Base>();
2195    String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2196    String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2197
2198    if (focus.size() == 1 && !Utilities.noString(regex)) {
2199      result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)));
2200    } else {
2201      result.add(new StringType(convertToString(focus.get(0))));
2202    }
2203    return result;
2204  }
2205
2206
2207  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2208    List<Base> result = new ArrayList<Base>();
2209    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2210
2211    if (focus.size() == 1 && !Utilities.noString(sw))
2212      result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)));
2213    else
2214      result.add(new BooleanType(false));
2215    return result;
2216  }
2217
2218
2219  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2220    List<Base> result = new ArrayList<Base>();
2221    result.add(new StringType(convertToString(focus)));
2222    return result;
2223  }
2224
2225
2226  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2227    String s = convertToString(focus);
2228    List<Base> result = new ArrayList<Base>();
2229    if (Utilities.isDecimal(s, true))
2230      result.add(new DecimalType(s));
2231    return result;
2232  }
2233
2234
2235  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2236    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2237    Boolean v = convertToBoolean(n1);
2238
2239    if (v)
2240      return execute(context, focus, exp.getParameters().get(1), true);
2241    else if (exp.getParameters().size() < 3)
2242      return new ArrayList<Base>();
2243    else
2244      return execute(context, focus, exp.getParameters().get(2), true);
2245  }
2246
2247
2248  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2249    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2250    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2251
2252    List<Base> result = new ArrayList<Base>();
2253    for (int i = 0; i < Math.min(focus.size(), i1); i++)
2254      result.add(focus.get(i));
2255    return result;
2256  }
2257
2258
2259  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2260    if (focus.size() == 1)
2261      return focus;
2262    throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()));
2263  }
2264
2265
2266  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2267    List<Base> result = new ArrayList<Base>();
2268    if (focus.size() == 0 || focus.size() > 1)
2269      result.add(new BooleanType(false));
2270    else {
2271      String tn = exp.getParameters().get(0).getName();
2272      result.add(new BooleanType(focus.get(0).hasType(tn)));
2273    }
2274    return result;
2275  }
2276
2277
2278  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2279    List<Base> result = new ArrayList<Base>();
2280    String tn = exp.getParameters().get(0).getName();
2281    for (Base b : focus)
2282      if (b.hasType(tn))
2283        result.add(b);
2284    return result;
2285  }
2286
2287
2288  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2289    List<Base> result = new ArrayList<Base>();
2290    List<Base> current = new ArrayList<Base>();
2291    current.addAll(focus);
2292    List<Base> added = new ArrayList<Base>();
2293    boolean more = true;
2294    while (more) {
2295      added.clear();
2296      List<Base> pc = new ArrayList<Base>();
2297      for (Base item : current) {
2298        pc.clear();
2299        pc.add(item);
2300        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
2301      }
2302      more = !added.isEmpty();
2303      result.addAll(added);
2304      current.clear();
2305      current.addAll(added);
2306    }
2307    return result;
2308  }
2309
2310
2311
2312  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2313    if (focus.size() <= 1)
2314      return makeBoolean(true);
2315
2316    boolean distinct = true;
2317    for (int i = 0; i < focus.size(); i++) {
2318      for (int j = i+1; j < focus.size(); j++) {
2319        if (doEquals(focus.get(j), focus.get(i))) {
2320          distinct = false;
2321          break;
2322        }
2323      }
2324    }
2325    return makeBoolean(distinct);
2326  }
2327
2328
2329  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2330    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2331
2332    boolean valid = true;
2333    for (Base item : target) {
2334      boolean found = false;
2335      for (Base t : focus) {
2336        if (Base.compareDeep(item, t, false)) {
2337          found = true;
2338          break;
2339        }
2340      }
2341      if (!found) {
2342        valid = false;
2343        break;
2344      }
2345    }
2346    List<Base> result = new ArrayList<Base>();
2347    result.add(new BooleanType(valid));
2348    return result;
2349  }
2350
2351
2352  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2353    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2354
2355    boolean valid = true;
2356    for (Base item : focus) {
2357      boolean found = false;
2358      for (Base t : target) {
2359        if (Base.compareDeep(item, t, false)) {
2360          found = true;
2361          break;
2362        }
2363      }
2364      if (!found) {
2365        valid = false;
2366        break;
2367      }
2368    }
2369    List<Base> result = new ArrayList<Base>();
2370    result.add(new BooleanType(valid));
2371    return result;
2372  }
2373
2374
2375  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2376    List<Base> result = new ArrayList<Base>();
2377    result.add(new BooleanType(!ElementUtil.isEmpty(focus)));
2378    return result;
2379  }
2380
2381
2382  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2383    List<Base> result = new ArrayList<Base>();
2384    for (Base item : focus) {
2385        String s = convertToString(item);
2386        if (item.fhirType().equals("Reference")) {
2387          Property p = item.getChildByName("reference");
2388        if (p.hasValues())
2389            s = convertToString(p.getValues().get(0));
2390        }
2391        Base res = null;
2392        if (s.startsWith("#")) {
2393          Property p = context.getResource().getChildByName("contained");
2394          for (Base c : p.getValues()) {
2395          if (s.equals(c.getIdBase()))
2396              res = c;
2397        }
2398      } else if (hostServices != null) {
2399         res = hostServices.resolveReference(context.getAppInfo(), s);
2400      }
2401        if (res != null)
2402          result.add(res);
2403      }
2404    return result;
2405  }
2406
2407        private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2408    List<Base> result = new ArrayList<Base>();
2409    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2410    String url = nl.get(0).primitiveValue();
2411
2412    for (Base item : focus) {
2413      List<Base> ext = new ArrayList<Base>();
2414      getChildrenByName(item, "extension", ext);
2415      getChildrenByName(item, "modifierExtension", ext);
2416      for (Base ex : ext) {
2417        List<Base> vl = new ArrayList<Base>();
2418        getChildrenByName(ex, "url", vl);
2419        if (convertToString(vl).equals(url))
2420          result.add(ex);
2421      }
2422    }
2423    return result;
2424  }
2425
2426        private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2427    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2428    String name = nl.get(0).primitiveValue();
2429
2430    log(name, focus);
2431    return focus;
2432  }
2433
2434  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2435    if (focus.size() <= 1)
2436      return focus;
2437
2438    List<Base> result = new ArrayList<Base>();
2439    for (int i = 0; i < focus.size(); i++) {
2440      boolean found = false;
2441      for (int j = i+1; j < focus.size(); j++) {
2442        if (doEquals(focus.get(j), focus.get(i))) {
2443          found = true;
2444          break;
2445        }
2446      }
2447      if (!found)
2448        result.add(focus.get(i));
2449    }
2450    return result;
2451  }
2452
2453        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2454    List<Base> result = new ArrayList<Base>();
2455    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2456
2457    if (focus.size() == 1 && !Utilities.noString(sw)) {
2458      String st = convertToString(focus.get(0));
2459      if (Utilities.noString(st))
2460        result.add(new BooleanType(false));
2461      else
2462        result.add(new BooleanType(st.matches(sw)));
2463    } else
2464      result.add(new BooleanType(false));
2465    return result;
2466  }
2467
2468        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2469    List<Base> result = new ArrayList<Base>();
2470    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2471
2472    if (focus.size() == 1 && !Utilities.noString(sw)) {
2473      String st = convertToString(focus.get(0));
2474      if (Utilities.noString(st))
2475        result.add(new BooleanType(false));
2476      else
2477        result.add(new BooleanType(st.contains(sw)));
2478    }  else
2479      result.add(new BooleanType(false));
2480    return result;
2481  }
2482
2483  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2484    List<Base> result = new ArrayList<Base>();
2485    if (focus.size() == 1) {
2486      String s = convertToString(focus.get(0));
2487      result.add(new IntegerType(s.length()));
2488    }
2489    return result;
2490  }
2491
2492  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2493    List<Base> result = new ArrayList<Base>();
2494    if (focus.size() == 1) {
2495      String s = convertToString(focus.get(0));
2496      result.add(new BooleanType(!Utilities.noString(s)));
2497    }
2498    return result;
2499  }
2500
2501        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2502    List<Base> result = new ArrayList<Base>();
2503    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2504
2505    if (focus.size() == 1 && !Utilities.noString(sw))
2506      result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw)));
2507    else
2508      result.add(new BooleanType(false));
2509    return result;
2510  }
2511
2512        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2513    List<Base> result = new ArrayList<Base>();
2514    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2515    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2516    int i2 = -1;
2517    if (exp.parameterCount() == 2) {
2518      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
2519      i2 = Integer.parseInt(n2.get(0).primitiveValue());
2520    }
2521
2522    if (focus.size() == 1) {
2523      String sw = convertToString(focus.get(0));
2524      String s;
2525      if (i1 < 0 || i1 >= sw.length())
2526        return new ArrayList<Base>();
2527      if (exp.parameterCount() == 2)
2528        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
2529      else
2530        s = sw.substring(i1);
2531      if (!Utilities.noString(s))
2532        result.add(new StringType(s));
2533    }
2534    return result;
2535  }
2536
2537  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2538    String s = convertToString(focus);
2539    List<Base> result = new ArrayList<Base>();
2540    if (Utilities.isInteger(s))
2541      result.add(new IntegerType(s));
2542    return result;
2543  }
2544
2545  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2546    List<Base> result = new ArrayList<Base>();
2547    result.add(new IntegerType(focus.size()));
2548    return result;
2549  }
2550
2551  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2552    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2553    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2554
2555    List<Base> result = new ArrayList<Base>();
2556    for (int i = i1; i < focus.size(); i++)
2557      result.add(focus.get(i));
2558    return result;
2559  }
2560
2561  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2562    List<Base> result = new ArrayList<Base>();
2563    for (int i = 1; i < focus.size(); i++)
2564      result.add(focus.get(i));
2565    return result;
2566  }
2567
2568  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2569    List<Base> result = new ArrayList<Base>();
2570    if (focus.size() > 0)
2571      result.add(focus.get(focus.size()-1));
2572    return result;
2573  }
2574
2575  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2576    List<Base> result = new ArrayList<Base>();
2577    if (focus.size() > 0)
2578      result.add(focus.get(0));
2579    return result;
2580  }
2581
2582
2583        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2584    List<Base> result = new ArrayList<Base>();
2585    List<Base> pc = new ArrayList<Base>();
2586    for (Base item : focus) {
2587      pc.clear();
2588      pc.add(item);
2589      if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)))
2590        result.add(item);
2591    }
2592    return result;
2593  }
2594
2595  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2596    List<Base> result = new ArrayList<Base>();
2597    List<Base> pc = new ArrayList<Base>();
2598    for (Base item : focus) {
2599      pc.clear();
2600      pc.add(item);
2601      result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
2602    }
2603    return result;
2604  }
2605
2606
2607        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2608    List<Base> result = new ArrayList<Base>();
2609    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2610    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size())
2611      result.add(focus.get(Integer.parseInt(s)));
2612    return result;
2613  }
2614
2615  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2616    List<Base> result = new ArrayList<Base>();
2617                result.add(new BooleanType(ElementUtil.isEmpty(focus)));
2618    return result;
2619  }
2620
2621  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2622    return makeBoolean(!convertToBoolean(focus));
2623  }
2624
2625  private class ElementDefinitionMatch {
2626    private ElementDefinition definition;
2627    private String fixedType;
2628    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
2629      super();
2630      this.definition = definition;
2631      this.fixedType = fixedType;
2632    }
2633    public ElementDefinition getDefinition() {
2634      return definition;
2635    }
2636    public String getFixedType() {
2637      return fixedType;
2638    }
2639
2640  }
2641
2642  private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException {
2643    if (Utilities.noString(type))
2644      throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName");
2645    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml"))
2646      return;
2647    String url = null;
2648    if (type.contains("#")) {
2649      url = type.substring(0, type.indexOf("#"));
2650    } else {
2651      url = type;
2652    }
2653    String tail = "";
2654    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
2655    if (sd == null)
2656      throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong
2657    List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
2658    ElementDefinitionMatch m = null;
2659    if (type.contains("#"))
2660      m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false);
2661    if (m != null && hasDataType(m.definition)) {
2662      if (m.fixedType != null)
2663      {
2664        StructureDefinition dt = worker.fetchTypeDefinition(m.fixedType);
2665        if (dt == null)
2666          throw new DefinitionException("unknown data type "+m.fixedType);
2667        sdl.add(dt);
2668      } else
2669        for (TypeRefComponent t : m.definition.getType()) {
2670          StructureDefinition dt = worker.fetchTypeDefinition(t.getCode());
2671          if (dt == null)
2672            throw new DefinitionException("unknown data type "+t.getCode());
2673          sdl.add(dt);
2674        }
2675    } else {
2676      sdl.add(sd);
2677      if (type.contains("#")) {
2678        tail = type.substring(type.indexOf("#")+1);
2679        tail = tail.substring(tail.indexOf("."));
2680      }
2681    }
2682
2683    for (StructureDefinition sdi : sdl) {
2684      String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
2685      if (name.equals("**")) {
2686        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2687        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2688          if (ed.getPath().startsWith(path))
2689            for (TypeRefComponent t : ed.getType()) {
2690              if (t.hasCode() && t.getCodeElement().hasValue()) {
2691                String tn = null;
2692                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2693                  tn = sdi.getType()+"#"+ed.getPath();
2694                else
2695                  tn = t.getCode();
2696                if (t.getCode().equals("Resource")) {
2697                  for (String rn : worker.getResourceNames()) {
2698                    if (!result.hasType(worker, rn)) {
2699                      getChildTypesByName(result.addType(rn), "**", result);
2700                    }
2701                  }
2702                } else if (!result.hasType(worker, tn)) {
2703                  getChildTypesByName(result.addType(tn), "**", result);
2704                }
2705              }
2706            }
2707        }
2708      } else if (name.equals("*")) {
2709        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2710        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2711          if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
2712            for (TypeRefComponent t : ed.getType()) {
2713              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2714                result.addType(sdi.getType()+"#"+ed.getPath());
2715              else if (t.getCode().equals("Resource"))
2716                result.addTypes(worker.getResourceNames());
2717              else
2718                result.addType(t.getCode());
2719            }
2720        }
2721      } else {
2722        path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
2723
2724        ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
2725        if (ed != null) {
2726          if (!Utilities.noString(ed.getFixedType()))
2727            result.addType(ed.getFixedType());
2728          else
2729            for (TypeRefComponent t : ed.getDefinition().getType()) {
2730              if (Utilities.noString(t.getCode()))
2731                break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
2732
2733              ProfiledType pt = null;
2734              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2735                pt = new ProfiledType(sdi.getUrl()+"#"+path);
2736              else if (t.getCode().equals("Resource"))
2737                result.addTypes(worker.getResourceNames());
2738              else
2739                pt = new ProfiledType(t.getCode());
2740              if (pt != null) {
2741                if (t.hasProfile())
2742                  pt.addProfile(t.getProfile());
2743                if (ed.getDefinition().hasBinding())
2744                  pt.addBinding(ed.getDefinition().getBinding());
2745                result.addType(pt);
2746              }
2747            }
2748        }
2749      }
2750    }
2751  }
2752
2753  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException {
2754    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2755      if (ed.getPath().equals(path)) {
2756        if (ed.hasContentReference()) {
2757          return getElementDefinitionById(sd, ed.getContentReference());
2758        } else
2759          return new ElementDefinitionMatch(ed, null);
2760      }
2761      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3)
2762        return new ElementDefinitionMatch(ed, null);
2763      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
2764        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
2765        if (primitiveTypes.contains(s))
2766          return new ElementDefinitionMatch(ed, s);
2767        else
2768        return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
2769      }
2770      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) {
2771        // now we walk into the type.
2772        if (ed.getType().size() > 1)  // if there's more than one type, the test above would fail this
2773          throw new PathEngineException("Internal typing issue....");
2774        StructureDefinition nsd = worker.fetchTypeDefinition(ed.getType().get(0).getCode());
2775            if (nsd == null)
2776              throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode());
2777        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName);
2778      }
2779      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
2780        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
2781        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName);
2782      }
2783    }
2784    return null;
2785  }
2786
2787  private boolean isAbstractType(List<TypeRefComponent> list) {
2788        return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
2789}
2790
2791
2792  private boolean hasType(ElementDefinition ed, String s) {
2793    for (TypeRefComponent t : ed.getType())
2794      if (s.equalsIgnoreCase(t.getCode()))
2795        return true;
2796    return false;
2797  }
2798
2799  private boolean hasDataType(ElementDefinition ed) {
2800    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
2801  }
2802
2803  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
2804    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2805      if (ref.equals("#"+ed.getId()))
2806        return new ElementDefinitionMatch(ed, null);
2807    }
2808    return null;
2809  }
2810
2811
2812  public boolean hasLog() {
2813    return log != null && log.length() > 0;
2814  }
2815
2816
2817  public String takeLog() {
2818    if (!hasLog())
2819      return "";
2820    String s = log.toString();
2821    log = new StringBuilder();
2822    return s;
2823  }
2824
2825}