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().copy());
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().copy());
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().copy());
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().copy());
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().copy());
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, Operation.As));
637    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Equals, Operation.Equivalent, Operation.NotEquals, Operation.NotEquivalent));
638    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.In, Operation.Contains));
639    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.And));
640    node = gatherPrecedence(lexer, node, EnumSet.of(Operation.Xor, Operation.Or));
641    // last: implies
642    return node;
643  }
644
645  private ExpressionNode gatherPrecedence(FHIRLexer lexer, ExpressionNode start, EnumSet<Operation> ops) {
646    //    work : boolean;
647    //    focus, node, group : ExpressionNode;
648
649    assert(start.isProximal());
650
651    // is there anything to do?
652    boolean work = false;
653    ExpressionNode focus = start.getOpNext();
654    if (ops.contains(start.getOperation())) {
655      while (focus != null && focus.getOperation() != null) {
656        work = work || !ops.contains(focus.getOperation());
657        focus = focus.getOpNext();
658      }
659    } else {
660      while (focus != null && focus.getOperation() != null) {
661        work = work || ops.contains(focus.getOperation());
662        focus = focus.getOpNext();
663      }
664    }
665    if (!work)
666      return start;
667
668    // entry point: tricky
669    ExpressionNode group;
670    if (ops.contains(start.getOperation())) {
671      group = newGroup(lexer, start);
672      group.setProximal(true);
673      focus = start;
674      start = group;
675    } else {
676      ExpressionNode node = start;
677
678      focus = node.getOpNext();
679      while (!ops.contains(focus.getOperation())) {
680        node = focus;
681        focus = focus.getOpNext();
682      }
683      group = newGroup(lexer, focus);
684      node.setOpNext(group);
685    }
686
687    // now, at this point:
688    //   group is the group we are adding to, it already has a .group property filled out.
689    //   focus points at the group.group
690    do {
691      // run until we find the end of the sequence
692      while (ops.contains(focus.getOperation()))
693        focus = focus.getOpNext();
694      if (focus.getOperation() != null) {
695        group.setOperation(focus.getOperation());
696        group.setOpNext(focus.getOpNext());
697        focus.setOperation(null);
698        focus.setOpNext(null);
699        // now look for another sequence, and start it
700        ExpressionNode node = group;
701        focus = group.getOpNext();
702        if (focus != null) {
703          while (focus != null && !ops.contains(focus.getOperation())) {
704            node = focus;
705            focus = focus.getOpNext();
706          }
707          if (focus != null) { // && (focus.Operation in Ops) - must be true
708            group = newGroup(lexer, focus);
709            node.setOpNext(group);
710          }
711        }
712      }
713    }
714    while (focus != null && focus.getOperation() != null);
715    return start;
716  }
717
718
719  private ExpressionNode newGroup(FHIRLexer lexer, ExpressionNode next) {
720    ExpressionNode result = new ExpressionNode(lexer.nextId());
721    result.setKind(Kind.Group);
722    result.setGroup(next);
723    result.getGroup().setProximal(true);
724    return result;
725  }
726
727  private void checkConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
728    if (s.startsWith("\'") && s.endsWith("\'")) {
729      int i = 1;
730      while (i < s.length()-1) {
731        char ch = s.charAt(i);
732        if (ch == '\\') {
733          switch (ch) {
734          case 't':
735          case 'r':
736          case 'n':
737          case 'f':
738          case '\'':
739          case '\\':
740          case '/':
741            i++;
742            break;
743          case 'u':
744            if (!Utilities.isHex("0x"+s.substring(i, i+4)))
745              throw lexer.error("Improper unicode escape \\u"+s.substring(i, i+4));
746            break;
747          default:
748            throw lexer.error("Unknown character escape \\"+ch);
749          }
750        } else
751          i++;
752      }
753    }
754  }
755
756  //  procedure CheckParamCount(c : integer);
757  //  begin
758  //    if exp.Parameters.Count <> c then
759  //      raise lexer.error('The function "'+exp.name+'" requires '+inttostr(c)+' parameters', offset);
760  //  end;
761
762  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int count) throws FHIRLexerException {
763    if (exp.getParameters().size() != count)
764      throw lexer.error("The function \""+exp.getName()+"\" requires "+Integer.toString(count)+" parameters", location.toString());
765    return true;
766  }
767
768  private boolean checkParamCount(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, int countMin, int countMax) throws FHIRLexerException {
769    if (exp.getParameters().size() < countMin || exp.getParameters().size() > countMax)
770      throw lexer.error("The function \""+exp.getName()+"\" requires between "+Integer.toString(countMin)+" and "+Integer.toString(countMax)+" parameters", location.toString());
771    return true;
772  }
773
774  private boolean checkParameters(FHIRLexer lexer, SourceLocation location, ExpressionNode exp, FunctionDetails details) throws FHIRLexerException {
775    switch (exp.getFunction()) {
776    case Empty: return checkParamCount(lexer, location, exp, 0);
777    case Not: return checkParamCount(lexer, location, exp, 0);
778    case Exists: return checkParamCount(lexer, location, exp, 0);
779    case SubsetOf: return checkParamCount(lexer, location, exp, 1);
780    case SupersetOf: return checkParamCount(lexer, location, exp, 1);
781    case IsDistinct: return checkParamCount(lexer, location, exp, 0);
782    case Distinct: return checkParamCount(lexer, location, exp, 0);
783    case Count: return checkParamCount(lexer, location, exp, 0);
784    case Where: return checkParamCount(lexer, location, exp, 1);
785    case Select: return checkParamCount(lexer, location, exp, 1);
786    case All: return checkParamCount(lexer, location, exp, 0, 1);
787    case Repeat: return checkParamCount(lexer, location, exp, 1);
788    case Item: return checkParamCount(lexer, location, exp, 1);
789    case As: return checkParamCount(lexer, location, exp, 1);
790    case Is: return checkParamCount(lexer, location, exp, 1);
791    case Single: return checkParamCount(lexer, location, exp, 0);
792    case First: return checkParamCount(lexer, location, exp, 0);
793    case Last: return checkParamCount(lexer, location, exp, 0);
794    case Tail: return checkParamCount(lexer, location, exp, 0);
795    case Skip: return checkParamCount(lexer, location, exp, 1);
796    case Take: return checkParamCount(lexer, location, exp, 1);
797    case Iif: return checkParamCount(lexer, location, exp, 2,3);
798    case ToInteger: return checkParamCount(lexer, location, exp, 0);
799    case ToDecimal: return checkParamCount(lexer, location, exp, 0);
800    case ToString: return checkParamCount(lexer, location, exp, 0);
801    case Substring: return checkParamCount(lexer, location, exp, 1, 2);
802    case StartsWith: return checkParamCount(lexer, location, exp, 1);
803    case EndsWith: return checkParamCount(lexer, location, exp, 1);
804    case Matches: return checkParamCount(lexer, location, exp, 1);
805    case ReplaceMatches: return checkParamCount(lexer, location, exp, 2);
806    case Contains: return checkParamCount(lexer, location, exp, 1);
807    case Replace: return checkParamCount(lexer, location, exp, 2);
808    case Length: return checkParamCount(lexer, location, exp, 0);
809    case Children: return checkParamCount(lexer, location, exp, 0);
810    case Descendants: return checkParamCount(lexer, location, exp, 0);
811    case MemberOf: return checkParamCount(lexer, location, exp, 1);
812    case Trace: return checkParamCount(lexer, location, exp, 1);
813    case Today: return checkParamCount(lexer, location, exp, 0);
814    case Now: return checkParamCount(lexer, location, exp, 0);
815    case Resolve: return checkParamCount(lexer, location, exp, 0);
816    case Extension: return checkParamCount(lexer, location, exp, 1);
817    case HasValue: return checkParamCount(lexer, location, exp, 0);
818    case Alias: return checkParamCount(lexer, location, exp, 1);
819    case AliasAs: return checkParamCount(lexer, location, exp, 1);
820    case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
821    }
822    return false;
823  }
824
825        private List<Base> execute(ExecutionContext context, List<Base> focus, ExpressionNode exp, boolean atEntry) throws FHIRException {
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        }
870        last = next;
871        next = next.getOpNext();
872      }
873    }
874    return work;
875  }
876
877  private List<Base> executeTypeName(ExecutionContext context, List<Base> focus, ExpressionNode next, boolean atEntry) {
878    List<Base> result = new ArrayList<Base>();
879    result.add(new StringType(next.getName()));
880    return result;
881  }
882
883
884  private List<Base> preOperate(List<Base> left, Operation operation) {
885    switch (operation) {
886    case And:
887      return isBoolean(left, false) ? makeBoolean(false) : null;
888    case Or:
889      return isBoolean(left, true) ? makeBoolean(true) : null;
890    case Implies:
891      return convertToBoolean(left) ? null : makeBoolean(true);
892    default:
893      return null;
894    }
895  }
896
897  private List<Base> makeBoolean(boolean b) {
898    List<Base> res = new ArrayList<Base>();
899    res.add(new BooleanType(b));
900    return res;
901  }
902
903  private TypeDetails executeTypeName(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
904    return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
905  }
906
907  private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
908    TypeDetails result = new TypeDetails(null);
909    switch (exp.getKind()) {
910    case Name:
911      if (atEntry && exp.getName().equals("$this"))
912        result.update(context.getThisItem());
913      else if (atEntry && focus == null)
914        result.update(executeContextType(context, exp.getName()));
915      else {
916        for (String s : focus.getTypes()) {
917          result.update(executeType(s, exp, atEntry));
918        }
919        if (result.hasNoTypes())
920          throw new PathEngineException("The name "+exp.getName()+" is not valid for any of the possible types: "+focus.describe());
921      }
922      break;
923    case Function:
924      result.update(evaluateFunctionType(context, focus, exp));
925      break;
926    case Constant:
927      result.update(readConstantType(context, exp.getConstant()));
928      break;
929    case Group:
930      result.update(executeType(context, focus, exp.getGroup(), atEntry));
931    }
932    exp.setTypes(result);
933
934    if (exp.getInner() != null) {
935      result = executeType(context, result, exp.getInner(), false);
936    }
937
938    if (exp.isProximal() && exp.getOperation() != null) {
939      ExpressionNode next = exp.getOpNext();
940      ExpressionNode last = exp;
941      while (next != null) {
942        TypeDetails work;
943        if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As)
944          work = executeTypeName(context, focus, next, atEntry);
945        else
946          work = executeType(context, focus, next, atEntry);
947        result = operateTypes(result, last.getOperation(), work);
948        last = next;
949        next = next.getOpNext();
950      }
951      exp.setOpTypes(result);
952    }
953    return result;
954  }
955
956  private Base processConstant(ExecutionContext context, String constant) throws PathEngineException {
957    if (constant.equals("true")) {
958      return new BooleanType(true);
959    } else if (constant.equals("false")) {
960      return new BooleanType(false);
961    } else if (constant.equals("{}")) {
962      return null;
963    } else if (Utilities.isInteger(constant)) {
964      return new IntegerType(constant);
965    } else if (Utilities.isDecimal(constant, false)) {
966      return new DecimalType(constant);
967    } else if (constant.startsWith("\'")) {
968      return new StringType(processConstantString(constant));
969    } else if (constant.startsWith("%")) {
970      return resolveConstant(context, constant);
971    } else if (constant.startsWith("@")) {
972      return processDateConstant(context.getAppInfo(), constant.substring(1));
973    } else {
974      return new StringType(constant);
975    }
976  }
977
978  private Base processDateConstant(Object appInfo, String value) throws PathEngineException {
979    if (value.startsWith("T"))
980      return new TimeType(value.substring(1));
981    String v = value;
982    if (v.length() > 10) {
983      int i = v.substring(10).indexOf("-");
984      if (i == -1)
985        i = v.substring(10).indexOf("+");
986      if (i == -1)
987        i = v.substring(10).indexOf("Z");
988      v = i == -1 ? value : v.substring(0,  10+i);
989    }
990    if (v.length() > 10)
991      return new DateTimeType(value);
992    else
993      return new DateType(value);
994  }
995
996
997  private Base resolveConstant(ExecutionContext context, String s) throws PathEngineException {
998    if (s.equals("%sct"))
999      return new StringType("http://snomed.info/sct");
1000    else if (s.equals("%loinc"))
1001      return new StringType("http://loinc.org");
1002    else if (s.equals("%ucum"))
1003      return new StringType("http://unitsofmeasure.org");
1004    else if (s.equals("%resource")) {
1005      if (context.getResource() == null)
1006        throw new PathEngineException("Cannot use %resource in this context");
1007      return context.getResource();
1008    } else if (s.equals("%context")) {
1009      return context.getContext();
1010    } else if (s.equals("%us-zip"))
1011      return new StringType("[0-9]{5}(-[0-9]{4}){0,1}");
1012    else if (s.startsWith("%\"vs-"))
1013      return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"");
1014    else if (s.startsWith("%\"cs-"))
1015      return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"");
1016    else if (s.startsWith("%\"ext-"))
1017      return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1));
1018    else if (hostServices == null)
1019      throw new PathEngineException("Unknown fixed constant '"+s+"'");
1020    else
1021      return hostServices.resolveConstant(context.getAppInfo(), s.substring(1));
1022  }
1023
1024
1025  private String processConstantString(String s) throws PathEngineException {
1026    StringBuilder b = new StringBuilder();
1027    int i = 1;
1028    while (i < s.length()-1) {
1029      char ch = s.charAt(i);
1030      if (ch == '\\') {
1031        i++;
1032        switch (s.charAt(i)) {
1033        case 't':
1034          b.append('\t');
1035          break;
1036        case 'r':
1037          b.append('\r');
1038          break;
1039        case 'n':
1040          b.append('\n');
1041          break;
1042        case 'f':
1043          b.append('\f');
1044          break;
1045        case '\'':
1046          b.append('\'');
1047          break;
1048        case '\\':
1049          b.append('\\');
1050          break;
1051        case '/':
1052          b.append('/');
1053          break;
1054        case 'u':
1055          i++;
1056          int uc = Integer.parseInt(s.substring(i, i+4), 16);
1057          b.append((char) uc);
1058          i = i + 3;
1059          break;
1060        default:
1061          throw new PathEngineException("Unknown character escape \\"+s.charAt(i));
1062        }
1063        i++;
1064      } else {
1065        b.append(ch);
1066        i++;
1067      }
1068    }
1069    return b.toString();
1070  }
1071
1072
1073  private List<Base> operate(List<Base> left, Operation operation, List<Base> right) throws FHIRException {
1074    switch (operation) {
1075    case Equals: return opEquals(left, right);
1076    case Equivalent: return opEquivalent(left, right);
1077    case NotEquals: return opNotEquals(left, right);
1078    case NotEquivalent: return opNotEquivalent(left, right);
1079    case LessThen: return opLessThen(left, right);
1080    case Greater: return opGreater(left, right);
1081    case LessOrEqual: return opLessOrEqual(left, right);
1082    case GreaterOrEqual: return opGreaterOrEqual(left, right);
1083    case Union: return opUnion(left, right);
1084    case In: return opIn(left, right);
1085    case Contains: return opContains(left, right);
1086    case Or:  return opOr(left, right);
1087    case And:  return opAnd(left, right);
1088    case Xor: return opXor(left, right);
1089    case Implies: return opImplies(left, right);
1090    case Plus: return opPlus(left, right);
1091    case Times: return opTimes(left, right);
1092    case Minus: return opMinus(left, right);
1093    case Concatenate: return opConcatenate(left, right);
1094    case DivideBy: return opDivideBy(left, right);
1095    case Div: return opDiv(left, right);
1096    case Mod: return opMod(left, right);
1097    case Is: return opIs(left, right);
1098    case As: return opAs(left, right);
1099    default:
1100      throw new Error("Not Done Yet: "+operation.toCode());
1101    }
1102  }
1103
1104  private List<Base> opAs(List<Base> left, List<Base> right) {
1105    List<Base> result = new ArrayList<Base>();
1106    if (left.size() != 1 || right.size() != 1)
1107      return result;
1108    else {
1109      String tn = convertToString(right);
1110      if (tn.equals(left.get(0).fhirType()))
1111        result.add(left.get(0));
1112    }
1113    return result;
1114  }
1115
1116
1117  private List<Base> opIs(List<Base> left, List<Base> right) {
1118    List<Base> result = new ArrayList<Base>();
1119    if (left.size() != 1 || right.size() != 1)
1120      result.add(new BooleanType(false));
1121    else {
1122      String tn = convertToString(right);
1123      result.add(new BooleanType(left.get(0).hasType(tn)));
1124    }
1125    return result;
1126  }
1127
1128
1129  private TypeDetails operateTypes(TypeDetails left, Operation operation, TypeDetails right) {
1130    switch (operation) {
1131    case Equals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1132    case Equivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1133    case NotEquals: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1134    case NotEquivalent: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1135    case LessThen: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1136    case Greater: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1137    case LessOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1138    case GreaterOrEqual: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1139    case Is: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1140    case As: return new TypeDetails(CollectionStatus.SINGLETON, right.getTypes());
1141    case Union: return left.union(right);
1142    case Or: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1143    case And: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1144    case Xor: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1145    case Implies : return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1146    case Times:
1147      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
1148      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1149        result.addType("integer");
1150      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1151        result.addType("decimal");
1152      return result;
1153    case DivideBy:
1154      result = new TypeDetails(CollectionStatus.SINGLETON);
1155      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1156        result.addType("decimal");
1157      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1158        result.addType("decimal");
1159      return result;
1160    case Concatenate:
1161      result = new TypeDetails(CollectionStatus.SINGLETON, "");
1162      return result;
1163    case Plus:
1164      result = new TypeDetails(CollectionStatus.SINGLETON);
1165      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1166        result.addType("integer");
1167      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1168        result.addType("decimal");
1169      else if (left.hasType(worker, "string", "id", "code", "uri") && right.hasType(worker, "string", "id", "code", "uri"))
1170        result.addType("string");
1171      return result;
1172    case Minus:
1173      result = new TypeDetails(CollectionStatus.SINGLETON);
1174      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1175        result.addType("integer");
1176      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1177        result.addType("decimal");
1178      return result;
1179    case Div:
1180    case Mod:
1181      result = new TypeDetails(CollectionStatus.SINGLETON);
1182      if (left.hasType(worker, "integer") && right.hasType(worker, "integer"))
1183        result.addType("integer");
1184      else if (left.hasType(worker, "integer", "decimal") && right.hasType(worker, "integer", "decimal"))
1185        result.addType("decimal");
1186      return result;
1187    case In: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1188    case Contains: return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1189    default:
1190      return null;
1191    }
1192  }
1193
1194
1195  private List<Base> opEquals(List<Base> left, List<Base> right) {
1196    if (left.size() != right.size())
1197      return makeBoolean(false);
1198
1199    boolean res = true;
1200    for (int i = 0; i < left.size(); i++) {
1201      if (!doEquals(left.get(i), right.get(i))) {
1202        res = false;
1203        break;
1204      }
1205    }
1206    return makeBoolean(res);
1207  }
1208
1209  private List<Base> opNotEquals(List<Base> left, List<Base> right) {
1210    if (left.size() != right.size())
1211      return makeBoolean(true);
1212
1213    boolean res = true;
1214    for (int i = 0; i < left.size(); i++) {
1215      if (!doEquals(left.get(i), right.get(i))) {
1216        res = false;
1217        break;
1218      }
1219    }
1220    return makeBoolean(!res);
1221  }
1222
1223  private boolean doEquals(Base left, Base right) {
1224    if (left.isPrimitive() && right.isPrimitive())
1225                        return Base.equals(left.primitiveValue(), right.primitiveValue());
1226    else
1227      return Base.compareDeep(left, right, false);
1228  }
1229
1230  private boolean doEquivalent(Base left, Base right) throws PathEngineException {
1231    if (left.hasType("integer") && right.hasType("integer"))
1232      return doEquals(left, right);
1233    if (left.hasType("boolean") && right.hasType("boolean"))
1234      return doEquals(left, right);
1235    if (left.hasType("integer", "decimal", "unsignedInt", "positiveInt") && right.hasType("integer", "decimal", "unsignedInt", "positiveInt"))
1236      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1237    if (left.hasType("date", "dateTime", "time", "instant") && right.hasType("date", "dateTime", "time", "instant"))
1238      return Utilities.equivalentNumber(left.primitiveValue(), right.primitiveValue());
1239    if (left.hasType("string", "id", "code", "uri") && right.hasType("string", "id", "code", "uri"))
1240      return Utilities.equivalent(convertToString(left), convertToString(right));
1241
1242    throw new PathEngineException(String.format("Unable to determine equivalence between %s and %s", left.fhirType(), right.fhirType()));
1243  }
1244
1245  private List<Base> opEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1246    if (left.size() != right.size())
1247      return makeBoolean(false);
1248
1249    boolean res = true;
1250    for (int i = 0; i < left.size(); i++) {
1251      boolean found = false;
1252      for (int j = 0; j < right.size(); j++) {
1253        if (doEquivalent(left.get(i), right.get(j))) {
1254          found = true;
1255          break;
1256        }
1257      }
1258      if (!found) {
1259        res = false;
1260        break;
1261      }
1262    }
1263    return makeBoolean(res);
1264  }
1265
1266  private List<Base> opNotEquivalent(List<Base> left, List<Base> right) throws PathEngineException {
1267    if (left.size() != right.size())
1268      return makeBoolean(true);
1269
1270    boolean res = true;
1271    for (int i = 0; i < left.size(); i++) {
1272      boolean found = false;
1273      for (int j = 0; j < right.size(); j++) {
1274        if (doEquivalent(left.get(i), right.get(j))) {
1275          found = true;
1276          break;
1277        }
1278      }
1279      if (!found) {
1280        res = false;
1281        break;
1282      }
1283    }
1284    return makeBoolean(!res);
1285  }
1286
1287        private List<Base> opLessThen(List<Base> left, List<Base> right) throws FHIRException {
1288    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1289      Base l = left.get(0);
1290      Base r = right.get(0);
1291      if (l.hasType("string") && r.hasType("string"))
1292        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1293      else if ((l.hasType("integer") || l.hasType("decimal")) && (r.hasType("integer") || r.hasType("decimal")))
1294        return makeBoolean(new Double(l.primitiveValue()) < new Double(r.primitiveValue()));
1295      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1296        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1297      else if ((l.hasType("time")) && (r.hasType("time")))
1298        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) < 0);
1299    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1300      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1301      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1302      if (Base.compareDeep(lUnit, rUnit, true)) {
1303        return opLessThen(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1304      } else {
1305                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1306      }
1307    }
1308    return new ArrayList<Base>();
1309  }
1310
1311        private List<Base> opGreater(List<Base> left, List<Base> right) throws FHIRException {
1312    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1313      Base l = left.get(0);
1314      Base r = right.get(0);
1315      if (l.hasType("string") && r.hasType("string"))
1316        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1317      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1318        return makeBoolean(new Double(l.primitiveValue()) > new Double(r.primitiveValue()));
1319      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1320        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1321      else if ((l.hasType("time")) && (r.hasType("time")))
1322        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) > 0);
1323    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1324      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1325      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1326      if (Base.compareDeep(lUnit, rUnit, true)) {
1327        return opGreater(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1328      } else {
1329                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1330      }
1331    }
1332    return new ArrayList<Base>();
1333  }
1334
1335        private List<Base> opLessOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1336    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1337      Base l = left.get(0);
1338      Base r = right.get(0);
1339      if (l.hasType("string") && r.hasType("string"))
1340        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1341      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1342        return makeBoolean(new Double(l.primitiveValue()) <= new Double(r.primitiveValue()));
1343      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1344        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1345      else if ((l.hasType("time")) && (r.hasType("time")))
1346        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) <= 0);
1347    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1348      List<Base> lUnits = left.get(0).listChildrenByName("unit");
1349      String lunit = lUnits.size() == 1 ? lUnits.get(0).primitiveValue() : null;
1350      List<Base> rUnits = right.get(0).listChildrenByName("unit");
1351      String runit = rUnits.size() == 1 ? rUnits.get(0).primitiveValue() : null;
1352      if ((lunit == null && runit == null) || lunit.equals(runit)) {
1353        return opLessOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1354      } else {
1355                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1356      }
1357    }
1358    return new ArrayList<Base>();
1359  }
1360
1361        private List<Base> opGreaterOrEqual(List<Base> left, List<Base> right) throws FHIRException {
1362    if (left.size() == 1 && right.size() == 1 && left.get(0).isPrimitive() && right.get(0).isPrimitive()) {
1363      Base l = left.get(0);
1364      Base r = right.get(0);
1365      if (l.hasType("string") && r.hasType("string"))
1366        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1367      else if ((l.hasType("integer", "decimal", "unsignedInt", "positiveInt")) && (r.hasType("integer", "decimal", "unsignedInt", "positiveInt")))
1368        return makeBoolean(new Double(l.primitiveValue()) >= new Double(r.primitiveValue()));
1369      else if ((l.hasType("date", "dateTime", "instant")) && (r.hasType("date", "dateTime", "instant")))
1370        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1371      else if ((l.hasType("time")) && (r.hasType("time")))
1372        return makeBoolean(l.primitiveValue().compareTo(r.primitiveValue()) >= 0);
1373    } else if (left.size() == 1 && right.size() == 1 && left.get(0).fhirType().equals("Quantity") && right.get(0).fhirType().equals("Quantity") ) {
1374      List<Base> lUnit = left.get(0).listChildrenByName("unit");
1375      List<Base> rUnit = right.get(0).listChildrenByName("unit");
1376      if (Base.compareDeep(lUnit, rUnit, true)) {
1377        return opGreaterOrEqual(left.get(0).listChildrenByName("value"), right.get(0).listChildrenByName("value"));
1378      } else {
1379                                throw new InternalErrorException("Canonical Comparison isn't done yet");
1380      }
1381    }
1382    return new ArrayList<Base>();
1383  }
1384
1385  private List<Base> opIn(List<Base> left, List<Base> right) {
1386    boolean ans = true;
1387    for (Base l : left) {
1388      boolean f = false;
1389      for (Base r : right)
1390        if (doEquals(l, r)) {
1391          f = true;
1392          break;
1393        }
1394      if (!f) {
1395        ans = false;
1396        break;
1397      }
1398    }
1399    return makeBoolean(ans);
1400  }
1401
1402  private List<Base> opContains(List<Base> left, List<Base> right) {
1403    boolean ans = true;
1404    for (Base r : right) {
1405      boolean f = false;
1406      for (Base l : left)
1407        if (doEquals(l, r)) {
1408          f = true;
1409          break;
1410        }
1411      if (!f) {
1412        ans = false;
1413        break;
1414      }
1415    }
1416    return makeBoolean(ans);
1417  }
1418
1419  private List<Base> opPlus(List<Base> left, List<Base> right) throws PathEngineException {
1420    if (left.size() == 0)
1421      throw new PathEngineException("Error performing +: left operand has no value");
1422    if (left.size() > 1)
1423      throw new PathEngineException("Error performing +: left operand has more than one value");
1424    if (!left.get(0).isPrimitive())
1425      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1426    if (right.size() == 0)
1427      throw new PathEngineException("Error performing +: right operand has no value");
1428    if (right.size() > 1)
1429      throw new PathEngineException("Error performing +: right operand has more than one value");
1430    if (!right.get(0).isPrimitive())
1431      throw new PathEngineException(String.format("Error performing +: right operand has the wrong type (%s)", right.get(0).fhirType()));
1432
1433    List<Base> result = new ArrayList<Base>();
1434    Base l = left.get(0);
1435    Base r = right.get(0);
1436    if (l.hasType("string", "id", "code", "uri") && r.hasType("string", "id", "code", "uri"))
1437      result.add(new StringType(l.primitiveValue() + r.primitiveValue()));
1438    else if (l.hasType("integer") && r.hasType("integer"))
1439      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) + Integer.parseInt(r.primitiveValue())));
1440    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1441      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).add(new BigDecimal(r.primitiveValue()))));
1442    else
1443      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()));
1444    return result;
1445  }
1446
1447  private List<Base> opTimes(List<Base> left, List<Base> right) throws PathEngineException {
1448    if (left.size() == 0)
1449      throw new PathEngineException("Error performing *: left operand has no value");
1450    if (left.size() > 1)
1451      throw new PathEngineException("Error performing *: left operand has more than one value");
1452    if (!left.get(0).isPrimitive())
1453      throw new PathEngineException(String.format("Error performing +: left operand has the wrong type (%s)", left.get(0).fhirType()));
1454    if (right.size() == 0)
1455      throw new PathEngineException("Error performing *: right operand has no value");
1456    if (right.size() > 1)
1457      throw new PathEngineException("Error performing *: right operand has more than one value");
1458    if (!right.get(0).isPrimitive())
1459      throw new PathEngineException(String.format("Error performing *: right operand has the wrong type (%s)", right.get(0).fhirType()));
1460
1461    List<Base> result = new ArrayList<Base>();
1462    Base l = left.get(0);
1463    Base r = right.get(0);
1464
1465    if (l.hasType("integer") && r.hasType("integer"))
1466      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) * Integer.parseInt(r.primitiveValue())));
1467    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1468      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).multiply(new BigDecimal(r.primitiveValue()))));
1469    else
1470      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()));
1471    return result;
1472  }
1473
1474  private List<Base> opConcatenate(List<Base> left, List<Base> right) {
1475    List<Base> result = new ArrayList<Base>();
1476    result.add(new StringType(convertToString(left) + convertToString((right))));
1477    return result;
1478  }
1479
1480  private List<Base> opUnion(List<Base> left, List<Base> right) {
1481    List<Base> result = new ArrayList<Base>();
1482    for (Base item : left) {
1483      if (!doContains(result, item))
1484        result.add(item);
1485    }
1486    for (Base item : right) {
1487      if (!doContains(result, item))
1488        result.add(item);
1489    }
1490    return result;
1491  }
1492
1493  private boolean doContains(List<Base> list, Base item) {
1494    for (Base test : list)
1495      if (doEquals(test, item))
1496        return true;
1497    return false;
1498  }
1499
1500
1501  private List<Base> opAnd(List<Base> left, List<Base> right) {
1502    if (left.isEmpty() && right.isEmpty())
1503      return new ArrayList<Base>();
1504    else if (isBoolean(left, false) || isBoolean(right, false))
1505      return makeBoolean(false);
1506    else if (left.isEmpty() || right.isEmpty())
1507      return new ArrayList<Base>();
1508    else if (convertToBoolean(left) && convertToBoolean(right))
1509      return makeBoolean(true);
1510    else
1511      return makeBoolean(false);
1512  }
1513
1514  private boolean isBoolean(List<Base> list, boolean b) {
1515    return list.size() == 1 && list.get(0) instanceof BooleanType && ((BooleanType) list.get(0)).booleanValue() == b;
1516  }
1517
1518  private List<Base> opOr(List<Base> left, List<Base> right) {
1519    if (left.isEmpty() && right.isEmpty())
1520      return new ArrayList<Base>();
1521    else if (convertToBoolean(left) || convertToBoolean(right))
1522      return makeBoolean(true);
1523    else if (left.isEmpty() || right.isEmpty())
1524      return new ArrayList<Base>();
1525    else
1526      return makeBoolean(false);
1527  }
1528
1529  private List<Base> opXor(List<Base> left, List<Base> right) {
1530    if (left.isEmpty() || right.isEmpty())
1531      return new ArrayList<Base>();
1532    else
1533      return makeBoolean(convertToBoolean(left) ^ convertToBoolean(right));
1534  }
1535
1536  private List<Base> opImplies(List<Base> left, List<Base> right) {
1537    if (!convertToBoolean(left))
1538      return makeBoolean(true);
1539    else if (right.size() == 0)
1540      return new ArrayList<Base>();
1541    else
1542      return makeBoolean(convertToBoolean(right));
1543  }
1544
1545
1546  private List<Base> opMinus(List<Base> left, List<Base> right) throws PathEngineException {
1547    if (left.size() == 0)
1548      throw new PathEngineException("Error performing -: left operand has no value");
1549    if (left.size() > 1)
1550      throw new PathEngineException("Error performing -: left operand has more than one value");
1551    if (!left.get(0).isPrimitive())
1552      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1553    if (right.size() == 0)
1554      throw new PathEngineException("Error performing -: right operand has no value");
1555    if (right.size() > 1)
1556      throw new PathEngineException("Error performing -: right operand has more than one value");
1557    if (!right.get(0).isPrimitive())
1558      throw new PathEngineException(String.format("Error performing -: right operand has the wrong type (%s)", right.get(0).fhirType()));
1559
1560    List<Base> result = new ArrayList<Base>();
1561    Base l = left.get(0);
1562    Base r = right.get(0);
1563
1564    if (l.hasType("integer") && r.hasType("integer"))
1565      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) - Integer.parseInt(r.primitiveValue())));
1566    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer"))
1567      result.add(new DecimalType(new BigDecimal(l.primitiveValue()).subtract(new BigDecimal(r.primitiveValue()))));
1568    else
1569      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()));
1570    return result;
1571  }
1572
1573  private List<Base> opDivideBy(List<Base> left, List<Base> right) throws PathEngineException {
1574    if (left.size() == 0)
1575      throw new PathEngineException("Error performing /: left operand has no value");
1576    if (left.size() > 1)
1577      throw new PathEngineException("Error performing /: left operand has more than one value");
1578    if (!left.get(0).isPrimitive())
1579      throw new PathEngineException(String.format("Error performing -: left operand has the wrong type (%s)", left.get(0).fhirType()));
1580    if (right.size() == 0)
1581      throw new PathEngineException("Error performing /: right operand has no value");
1582    if (right.size() > 1)
1583      throw new PathEngineException("Error performing /: right operand has more than one value");
1584    if (!right.get(0).isPrimitive())
1585      throw new PathEngineException(String.format("Error performing /: right operand has the wrong type (%s)", right.get(0).fhirType()));
1586
1587    List<Base> result = new ArrayList<Base>();
1588    Base l = left.get(0);
1589    Base r = right.get(0);
1590
1591    if (l.hasType("integer", "decimal", "unsignedInt", "positiveInt") && r.hasType("integer", "decimal", "unsignedInt", "positiveInt")) {
1592      Decimal d1;
1593      try {
1594        d1 = new Decimal(l.primitiveValue());
1595        Decimal d2 = new Decimal(r.primitiveValue());
1596        result.add(new DecimalType(d1.divide(d2).asDecimal()));
1597      } catch (UcumException e) {
1598        throw new PathEngineException(e);
1599      }
1600    }
1601    else
1602      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()));
1603    return result;
1604  }
1605
1606  private List<Base> opDiv(List<Base> left, List<Base> right) throws PathEngineException {
1607    if (left.size() == 0)
1608      throw new PathEngineException("Error performing div: left operand has no value");
1609    if (left.size() > 1)
1610      throw new PathEngineException("Error performing div: left operand has more than one value");
1611    if (!left.get(0).isPrimitive())
1612      throw new PathEngineException(String.format("Error performing div: left operand has the wrong type (%s)", left.get(0).fhirType()));
1613    if (right.size() == 0)
1614      throw new PathEngineException("Error performing div: right operand has no value");
1615    if (right.size() > 1)
1616      throw new PathEngineException("Error performing div: right operand has more than one value");
1617    if (!right.get(0).isPrimitive())
1618      throw new PathEngineException(String.format("Error performing div: right operand has the wrong type (%s)", right.get(0).fhirType()));
1619
1620    List<Base> result = new ArrayList<Base>();
1621    Base l = left.get(0);
1622    Base r = right.get(0);
1623
1624    if (l.hasType("integer") && r.hasType("integer"))
1625      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) / Integer.parseInt(r.primitiveValue())));
1626    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
1627      Decimal d1;
1628      try {
1629        d1 = new Decimal(l.primitiveValue());
1630        Decimal d2 = new Decimal(r.primitiveValue());
1631        result.add(new IntegerType(d1.divInt(d2).asDecimal()));
1632      } catch (UcumException e) {
1633        throw new PathEngineException(e);
1634      }
1635    }
1636    else
1637      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()));
1638    return result;
1639  }
1640
1641  private List<Base> opMod(List<Base> left, List<Base> right) throws PathEngineException {
1642    if (left.size() == 0)
1643      throw new PathEngineException("Error performing mod: left operand has no value");
1644    if (left.size() > 1)
1645      throw new PathEngineException("Error performing mod: left operand has more than one value");
1646    if (!left.get(0).isPrimitive())
1647      throw new PathEngineException(String.format("Error performing mod: left operand has the wrong type (%s)", left.get(0).fhirType()));
1648    if (right.size() == 0)
1649      throw new PathEngineException("Error performing mod: right operand has no value");
1650    if (right.size() > 1)
1651      throw new PathEngineException("Error performing mod: right operand has more than one value");
1652    if (!right.get(0).isPrimitive())
1653      throw new PathEngineException(String.format("Error performing mod: right operand has the wrong type (%s)", right.get(0).fhirType()));
1654
1655    List<Base> result = new ArrayList<Base>();
1656    Base l = left.get(0);
1657    Base r = right.get(0);
1658
1659    if (l.hasType("integer") && r.hasType("integer"))
1660      result.add(new IntegerType(Integer.parseInt(l.primitiveValue()) % Integer.parseInt(r.primitiveValue())));
1661    else if (l.hasType("decimal", "integer") && r.hasType("decimal", "integer")) {
1662      Decimal d1;
1663      try {
1664        d1 = new Decimal(l.primitiveValue());
1665        Decimal d2 = new Decimal(r.primitiveValue());
1666        result.add(new DecimalType(d1.modulo(d2).asDecimal()));
1667      } catch (UcumException e) {
1668        throw new PathEngineException(e);
1669      }
1670    }
1671    else
1672      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()));
1673    return result;
1674  }
1675
1676
1677  private TypeDetails readConstantType(ExecutionTypeContext context, String constant) throws PathEngineException {
1678    if (constant.equals("true"))
1679      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1680    else if (constant.equals("false"))
1681      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1682    else if (Utilities.isInteger(constant))
1683      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1684    else if (Utilities.isDecimal(constant, false))
1685      return new TypeDetails(CollectionStatus.SINGLETON, "decimal");
1686    else if (constant.startsWith("%"))
1687      return resolveConstantType(context, constant);
1688    else
1689      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1690  }
1691
1692  private TypeDetails resolveConstantType(ExecutionTypeContext context, String s) throws PathEngineException {
1693    if (s.equals("%sct"))
1694      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1695    else if (s.equals("%loinc"))
1696      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1697    else if (s.equals("%ucum"))
1698      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1699    else if (s.equals("%resource")) {
1700      if (context.getResource() == null)
1701        throw new PathEngineException("%resource cannot be used in this context");
1702      return new TypeDetails(CollectionStatus.SINGLETON, context.getResource());
1703    } else if (s.equals("%context")) {
1704      return new TypeDetails(CollectionStatus.SINGLETON, context.getContext());
1705    } else if (s.equals("%map-codes"))
1706      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1707    else if (s.equals("%us-zip"))
1708      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1709    else if (s.startsWith("%\"vs-"))
1710      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1711    else if (s.startsWith("%\"cs-"))
1712      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1713    else if (s.startsWith("%\"ext-"))
1714      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1715    else if (hostServices == null)
1716      throw new PathEngineException("Unknown fixed constant type for '"+s+"'");
1717    else
1718      return hostServices.resolveConstantType(context.getAppInfo(), s);
1719  }
1720
1721        private List<Base> execute(ExecutionContext context, Base item, ExpressionNode exp, boolean atEntry) throws FHIRException {
1722    List<Base> result = new ArrayList<Base>();
1723    if (atEntry && Character.isUpperCase(exp.getName().charAt(0))) {// special case for start up
1724      if (item.isResource() && item.fhirType().equals(exp.getName()))
1725        result.add(item);
1726    } else
1727      getChildrenByName(item, exp.getName(), result);
1728    if (result.size() == 0 && atEntry && context.getAppInfo() != null) {
1729      Base temp = hostServices.resolveConstant(context.getAppInfo(), exp.getName());
1730      if (temp != null) {
1731        result.add(temp);
1732      }
1733    }
1734    return result;
1735  }
1736
1737  private TypeDetails executeContextType(ExecutionTypeContext context, String name) throws PathEngineException, DefinitionException {
1738    if (hostServices == null)
1739      throw new PathEngineException("Unable to resolve context reference since no host services are provided");
1740    return hostServices.resolveConstantType(context.getAppInfo(), name);
1741  }
1742
1743  private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
1744    if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) // special case for start up
1745      return new TypeDetails(CollectionStatus.SINGLETON, type);
1746    TypeDetails result = new TypeDetails(null);
1747    getChildTypesByName(type, exp.getName(), result);
1748    return result;
1749  }
1750
1751
1752  private String hashTail(String type) {
1753    return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
1754  }
1755
1756
1757  @SuppressWarnings("unchecked")
1758  private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
1759    List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
1760    if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As)
1761      paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, "string"));
1762    else
1763      for (ExpressionNode expr : exp.getParameters()) {
1764        if (exp.getFunction() == Function.Where || exp.getFunction() == Function.Select || exp.getFunction() == Function.Repeat)
1765          paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
1766        else
1767          paramTypes.add(executeType(context, focus, expr, true));
1768      }
1769    switch (exp.getFunction()) {
1770    case Empty :
1771      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1772    case Not :
1773      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1774    case Exists :
1775      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1776    case SubsetOf : {
1777      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus);
1778      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1779    }
1780    case SupersetOf : {
1781      checkParamTypes(exp.getFunction().toCode(), paramTypes, focus);
1782      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1783    }
1784    case IsDistinct :
1785      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1786    case Distinct :
1787      return focus;
1788    case Count :
1789      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1790    case Where :
1791      return focus;
1792    case Select :
1793      return anything(focus.getCollectionStatus());
1794    case All :
1795      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1796    case Repeat :
1797      return anything(focus.getCollectionStatus());
1798    case Item : {
1799      checkOrdered(focus, "item");
1800      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1801      return focus;
1802    }
1803    case As : {
1804      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1805      return new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
1806    }
1807    case Is : {
1808      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1809      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1810    }
1811    case Single :
1812      return focus.toSingleton();
1813    case First : {
1814      checkOrdered(focus, "first");
1815      return focus.toSingleton();
1816    }
1817    case Last : {
1818      checkOrdered(focus, "last");
1819      return focus.toSingleton();
1820    }
1821    case Tail : {
1822      checkOrdered(focus, "tail");
1823      return focus;
1824    }
1825    case Skip : {
1826      checkOrdered(focus, "skip");
1827      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1828      return focus;
1829    }
1830    case Take : {
1831      checkOrdered(focus, "take");
1832      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1833      return focus;
1834    }
1835    case Iif : {
1836      TypeDetails types = new TypeDetails(null);
1837      types.update(paramTypes.get(0));
1838      if (paramTypes.size() > 1)
1839        types.update(paramTypes.get(1));
1840      return types;
1841    }
1842    case ToInteger : {
1843      checkContextPrimitive(focus, "toInteger");
1844      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1845    }
1846    case ToDecimal : {
1847      checkContextPrimitive(focus, "toDecimal");
1848      return new TypeDetails(CollectionStatus.SINGLETON, "decimal");
1849    }
1850    case ToString : {
1851      checkContextPrimitive(focus, "toString");
1852      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1853    }
1854    case Substring : {
1855      checkContextString(focus, "subString");
1856      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "integer"), new TypeDetails(CollectionStatus.SINGLETON, "integer"));
1857      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1858    }
1859    case StartsWith : {
1860      checkContextString(focus, "startsWith");
1861      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1862      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1863    }
1864    case EndsWith : {
1865      checkContextString(focus, "endsWith");
1866      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1867      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1868    }
1869    case Matches : {
1870      checkContextString(focus, "matches");
1871      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1872      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1873    }
1874    case ReplaceMatches : {
1875      checkContextString(focus, "replaceMatches");
1876      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string"));
1877      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1878    }
1879    case Contains : {
1880      checkContextString(focus, "contains");
1881      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1882      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1883    }
1884    case Replace : {
1885      checkContextString(focus, "replace");
1886      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"), new TypeDetails(CollectionStatus.SINGLETON, "string"));
1887      return new TypeDetails(CollectionStatus.SINGLETON, "string");
1888    }
1889    case Length : {
1890      checkContextPrimitive(focus, "length");
1891      return new TypeDetails(CollectionStatus.SINGLETON, "integer");
1892    }
1893    case Children :
1894      return childTypes(focus, "*");
1895    case Descendants :
1896      return childTypes(focus, "**");
1897    case MemberOf : {
1898      checkContextCoded(focus, "memberOf");
1899      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1900      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1901    }
1902    case Trace : {
1903      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1904      return focus;
1905    }
1906    case Today :
1907      return new TypeDetails(CollectionStatus.SINGLETON, "date");
1908    case Now :
1909      return new TypeDetails(CollectionStatus.SINGLETON, "dateTime");
1910    case Resolve : {
1911      checkContextReference(focus, "resolve");
1912      return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource");
1913    }
1914    case Extension : {
1915      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1916      return new TypeDetails(CollectionStatus.SINGLETON, "Extension");
1917    }
1918    case HasValue :
1919      return new TypeDetails(CollectionStatus.SINGLETON, "boolean");
1920    case Alias :
1921      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1922      return anything(CollectionStatus.SINGLETON);
1923    case AliasAs :
1924      checkParamTypes(exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, "string"));
1925      return focus;
1926    case Custom : {
1927      return hostServices.checkFunction(context.getAppInfo(), exp.getName(), paramTypes);
1928    }
1929    default:
1930      break;
1931    }
1932    throw new Error("not Implemented yet");
1933  }
1934
1935
1936  private void checkParamTypes(String funcName, List<TypeDetails> paramTypes, TypeDetails... typeSet) throws PathEngineException {
1937    int i = 0;
1938    for (TypeDetails pt : typeSet) {
1939      if (i == paramTypes.size())
1940        return;
1941      TypeDetails actual = paramTypes.get(i);
1942      i++;
1943      for (String a : actual.getTypes()) {
1944        if (!pt.hasType(worker, a))
1945          throw new PathEngineException("The parameter type '"+a+"' is not legal for "+funcName+" parameter "+Integer.toString(i)+". expecting "+pt.toString());
1946      }
1947    }
1948  }
1949
1950  private void checkOrdered(TypeDetails focus, String name) throws PathEngineException {
1951    if (focus.getCollectionStatus() == CollectionStatus.UNORDERED)
1952      throw new PathEngineException("The function '"+name+"'() can only be used on ordered collections");
1953  }
1954
1955  private void checkContextReference(TypeDetails focus, String name) throws PathEngineException {
1956    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Reference"))
1957      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, Reference");
1958  }
1959
1960
1961  private void checkContextCoded(TypeDetails focus, String name) throws PathEngineException {
1962    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "Coding") && !focus.hasType(worker, "CodeableConcept"))
1963      throw new PathEngineException("The function '"+name+"'() can only be used on string, code, uri, Coding, CodeableConcept");
1964  }
1965
1966
1967  private void checkContextString(TypeDetails focus, String name) throws PathEngineException {
1968    if (!focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "id"))
1969      throw new PathEngineException("The function '"+name+"'() can only be used on string, uri, code, id, but found "+focus.describe());
1970  }
1971
1972
1973  private void checkContextPrimitive(TypeDetails focus, String name) throws PathEngineException {
1974    if (!focus.hasType(primitiveTypes))
1975      throw new PathEngineException("The function '"+name+"'() can only be used on "+primitiveTypes.toString());
1976  }
1977
1978
1979  private TypeDetails childTypes(TypeDetails focus, String mask) throws PathEngineException, DefinitionException {
1980    TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
1981    for (String f : focus.getTypes())
1982      getChildTypesByName(f, mask, result);
1983    return result;
1984  }
1985
1986  private TypeDetails anything(CollectionStatus status) {
1987    return new TypeDetails(status, allTypes.keySet());
1988  }
1989
1990  //    private boolean isPrimitiveType(String s) {
1991  //            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");
1992  //    }
1993
1994        private List<Base> evaluateFunction(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
1995    switch (exp.getFunction()) {
1996    case Empty : return funcEmpty(context, focus, exp);
1997    case Not : return funcNot(context, focus, exp);
1998    case Exists : return funcExists(context, focus, exp);
1999    case SubsetOf : return funcSubsetOf(context, focus, exp);
2000    case SupersetOf : return funcSupersetOf(context, focus, exp);
2001    case IsDistinct : return funcIsDistinct(context, focus, exp);
2002    case Distinct : return funcDistinct(context, focus, exp);
2003    case Count : return funcCount(context, focus, exp);
2004    case Where : return funcWhere(context, focus, exp);
2005    case Select : return funcSelect(context, focus, exp);
2006    case All : return funcAll(context, focus, exp);
2007    case Repeat : return funcRepeat(context, focus, exp);
2008    case Item : return funcItem(context, focus, exp);
2009    case As : return funcAs(context, focus, exp);
2010    case Is : return funcIs(context, focus, exp);
2011    case Single : return funcSingle(context, focus, exp);
2012    case First : return funcFirst(context, focus, exp);
2013    case Last : return funcLast(context, focus, exp);
2014    case Tail : return funcTail(context, focus, exp);
2015    case Skip : return funcSkip(context, focus, exp);
2016    case Take : return funcTake(context, focus, exp);
2017    case Iif : return funcIif(context, focus, exp);
2018    case ToInteger : return funcToInteger(context, focus, exp);
2019    case ToDecimal : return funcToDecimal(context, focus, exp);
2020    case ToString : return funcToString(context, focus, exp);
2021    case Substring : return funcSubstring(context, focus, exp);
2022    case StartsWith : return funcStartsWith(context, focus, exp);
2023    case EndsWith : return funcEndsWith(context, focus, exp);
2024    case Matches : return funcMatches(context, focus, exp);
2025    case ReplaceMatches : return funcReplaceMatches(context, focus, exp);
2026    case Contains : return funcContains(context, focus, exp);
2027    case Replace : return funcReplace(context, focus, exp);
2028    case Length : return funcLength(context, focus, exp);
2029    case Children : return funcChildren(context, focus, exp);
2030    case Descendants : return funcDescendants(context, focus, exp);
2031    case MemberOf : return funcMemberOf(context, focus, exp);
2032    case Trace : return funcTrace(context, focus, exp);
2033    case Today : return funcToday(context, focus, exp);
2034    case Now : return funcNow(context, focus, exp);
2035    case Resolve : return funcResolve(context, focus, exp);
2036    case Extension : return funcExtension(context, focus, exp);
2037    case HasValue : return funcHasValue(context, focus, exp);
2038    case AliasAs : return funcAliasAs(context, focus, exp);
2039    case Alias : return funcAlias(context, focus, exp);
2040    case Custom: {
2041      List<List<Base>> params = new ArrayList<List<Base>>();
2042      for (ExpressionNode p : exp.getParameters())
2043        params.add(execute(context, focus, p, true));
2044      return hostServices.executeFunction(context.getAppInfo(), exp.getName(), params);
2045    }
2046    default:
2047      throw new Error("not Implemented yet");
2048    }
2049  }
2050
2051        private List<Base> funcAliasAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2052    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2053    String name = nl.get(0).primitiveValue();
2054    context.addAlias(name, focus);
2055    return focus;
2056  }
2057
2058  private List<Base> funcAlias(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2059    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2060    String name = nl.get(0).primitiveValue();
2061    List<Base> res = new ArrayList<Base>();
2062    Base b = context.getAlias(name);
2063    if (b != null)
2064      res.add(b);
2065    return res;
2066
2067  }
2068
2069  private List<Base> funcAll(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2070    if (exp.getParameters().size() == 1) {
2071      List<Base> result = new ArrayList<Base>();
2072      List<Base> pc = new ArrayList<Base>();
2073      boolean all = true;
2074      for (Base item : focus) {
2075        pc.clear();
2076        pc.add(item);
2077        if (!convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true))) {
2078          all = false;
2079          break;
2080        }
2081      }
2082      result.add(new BooleanType(all));
2083      return result;
2084    } else {// (exp.getParameters().size() == 0) {
2085      List<Base> result = new ArrayList<Base>();
2086      boolean all = true;
2087      for (Base item : focus) {
2088        boolean v = false;
2089        if (item instanceof BooleanType) {
2090          v = ((BooleanType) item).booleanValue();
2091        } else
2092          v = item != null;
2093        if (!v) {
2094          all = false;
2095          break;
2096        }
2097      }
2098      result.add(new BooleanType(all));
2099      return result;
2100    }
2101  }
2102
2103
2104  private ExecutionContext changeThis(ExecutionContext context, Base newThis) {
2105    return new ExecutionContext(context.getAppInfo(), context.getResource(), context.getContext(), context.getAliases(), newThis);
2106  }
2107
2108  private ExecutionTypeContext changeThis(ExecutionTypeContext context, TypeDetails newThis) {
2109    return new ExecutionTypeContext(context.getAppInfo(), context.getResource(), context.getContext(), newThis);
2110  }
2111
2112
2113  private List<Base> funcNow(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2114    List<Base> result = new ArrayList<Base>();
2115    result.add(DateTimeType.now());
2116    return result;
2117  }
2118
2119
2120  private List<Base> funcToday(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2121    List<Base> result = new ArrayList<Base>();
2122    result.add(new DateType(new Date(), TemporalPrecisionEnum.DAY));
2123    return result;
2124  }
2125
2126
2127  private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2128    throw new Error("not Implemented yet");
2129  }
2130
2131
2132  private List<Base> funcDescendants(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2133    List<Base> result = new ArrayList<Base>();
2134    List<Base> current = new ArrayList<Base>();
2135    current.addAll(focus);
2136    List<Base> added = new ArrayList<Base>();
2137    boolean more = true;
2138    while (more) {
2139      added.clear();
2140      for (Base item : current) {
2141        getChildrenByName(item, "*", added);
2142      }
2143      more = !added.isEmpty();
2144      result.addAll(added);
2145      current.clear();
2146      current.addAll(added);
2147    }
2148    return result;
2149  }
2150
2151
2152  private List<Base> funcChildren(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2153    List<Base> result = new ArrayList<Base>();
2154    for (Base b : focus)
2155      getChildrenByName(b, "*", result);
2156    return result;
2157  }
2158
2159
2160  private List<Base> funcReplace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException, PathEngineException {
2161    List<Base> result = new ArrayList<Base>();
2162
2163    if (focus.size() == 1) {
2164      String f = convertToString(focus.get(0));
2165
2166      if (!Utilities.noString(f)) {
2167
2168        if (exp.getParameters().size() == 2) {
2169
2170          String t = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2171          String r = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2172
2173          String n = f.replace(t, r);
2174          result.add(new StringType(n));
2175        }
2176        else {
2177          throw new PathEngineException(String.format("funcReplace() : checking for 2 arguments (pattern, substitution) but found %d items", exp.getParameters().size()));
2178        }
2179      }
2180      else {
2181        throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found empty item"));
2182      }
2183    }
2184    else {
2185      throw new PathEngineException(String.format("funcReplace() : checking for 1 string item but found %d items", focus.size()));
2186    }
2187    return result;
2188  }
2189
2190
2191  private List<Base> funcReplaceMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2192    List<Base> result = new ArrayList<Base>();
2193    String regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2194    String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2195
2196    if (focus.size() == 1 && !Utilities.noString(regex)) {
2197      result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)));
2198    } else {
2199      result.add(new StringType(convertToString(focus.get(0))));
2200    }
2201    return result;
2202  }
2203
2204
2205  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2206    List<Base> result = new ArrayList<Base>();
2207    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2208
2209    if (focus.size() == 1 && !Utilities.noString(sw))
2210      result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)));
2211    else
2212      result.add(new BooleanType(false));
2213    return result;
2214  }
2215
2216
2217  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2218    List<Base> result = new ArrayList<Base>();
2219    result.add(new StringType(convertToString(focus)));
2220    return result;
2221  }
2222
2223
2224  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2225    String s = convertToString(focus);
2226    List<Base> result = new ArrayList<Base>();
2227    if (Utilities.isDecimal(s, true))
2228      result.add(new DecimalType(s));
2229    return result;
2230  }
2231
2232
2233  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2234    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2235    Boolean v = convertToBoolean(n1);
2236
2237    if (v)
2238      return execute(context, focus, exp.getParameters().get(1), true);
2239    else if (exp.getParameters().size() < 3)
2240      return new ArrayList<Base>();
2241    else
2242      return execute(context, focus, exp.getParameters().get(2), true);
2243  }
2244
2245
2246  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2247    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2248    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2249
2250    List<Base> result = new ArrayList<Base>();
2251    for (int i = 0; i < Math.min(focus.size(), i1); i++)
2252      result.add(focus.get(i));
2253    return result;
2254  }
2255
2256
2257  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2258    if (focus.size() == 1)
2259      return focus;
2260    throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()));
2261  }
2262
2263
2264  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2265    List<Base> result = new ArrayList<Base>();
2266    if (focus.size() == 0 || focus.size() > 1)
2267      result.add(new BooleanType(false));
2268    else {
2269      String tn = exp.getParameters().get(0).getName();
2270      result.add(new BooleanType(focus.get(0).hasType(tn)));
2271    }
2272    return result;
2273  }
2274
2275
2276  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2277    List<Base> result = new ArrayList<Base>();
2278    String tn = exp.getParameters().get(0).getName();
2279    for (Base b : focus)
2280      if (b.hasType(tn))
2281        result.add(b);
2282    return result;
2283  }
2284
2285
2286  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2287    List<Base> result = new ArrayList<Base>();
2288    List<Base> current = new ArrayList<Base>();
2289    current.addAll(focus);
2290    List<Base> added = new ArrayList<Base>();
2291    boolean more = true;
2292    while (more) {
2293      added.clear();
2294      List<Base> pc = new ArrayList<Base>();
2295      for (Base item : current) {
2296        pc.clear();
2297        pc.add(item);
2298        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
2299      }
2300      more = !added.isEmpty();
2301      result.addAll(added);
2302      current.clear();
2303      current.addAll(added);
2304    }
2305    return result;
2306  }
2307
2308
2309
2310  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2311    if (focus.size() <= 1)
2312      return makeBoolean(true);
2313
2314    boolean distinct = true;
2315    for (int i = 0; i < focus.size(); i++) {
2316      for (int j = i+1; j < focus.size(); j++) {
2317        if (doEquals(focus.get(j), focus.get(i))) {
2318          distinct = false;
2319          break;
2320        }
2321      }
2322    }
2323    return makeBoolean(distinct);
2324  }
2325
2326
2327  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2328    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2329
2330    boolean valid = true;
2331    for (Base item : target) {
2332      boolean found = false;
2333      for (Base t : focus) {
2334        if (Base.compareDeep(item, t, false)) {
2335          found = true;
2336          break;
2337        }
2338      }
2339      if (!found) {
2340        valid = false;
2341        break;
2342      }
2343    }
2344    List<Base> result = new ArrayList<Base>();
2345    result.add(new BooleanType(valid));
2346    return result;
2347  }
2348
2349
2350  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2351    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2352
2353    boolean valid = true;
2354    for (Base item : focus) {
2355      boolean found = false;
2356      for (Base t : target) {
2357        if (Base.compareDeep(item, t, false)) {
2358          found = true;
2359          break;
2360        }
2361      }
2362      if (!found) {
2363        valid = false;
2364        break;
2365      }
2366    }
2367    List<Base> result = new ArrayList<Base>();
2368    result.add(new BooleanType(valid));
2369    return result;
2370  }
2371
2372
2373  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2374    List<Base> result = new ArrayList<Base>();
2375    result.add(new BooleanType(!ElementUtil.isEmpty(focus)));
2376    return result;
2377  }
2378
2379
2380  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2381    List<Base> result = new ArrayList<Base>();
2382    for (Base item : focus) {
2383      String s = convertToString(item);
2384      if (item.fhirType().equals("Reference")) {
2385        Property p = item.getChildByName("reference");
2386        if (p != null && p.hasValues()) {
2387          s = convertToString(p.getValues().get(0));
2388        } else {
2389          s = null; // a reference without any valid actual reference (just identifier or display, but we can't resolve it)
2390        }
2391      }
2392      if (item.fhirType().equals("canonical")) {
2393        s = item.primitiveValue();
2394      }
2395      if (s != null) {
2396        Base res = null;
2397        if (s.startsWith("#")) {
2398          String t = s.substring(1);
2399          Property p = context.getResource().getChildByName("contained");
2400          if (p != null) {
2401            for (Base c : p.getValues()) {
2402              if (t.equals(c.getIdBase())) {
2403                res = c;
2404                break;
2405              }
2406            }
2407          }
2408        } else if (hostServices != null) {
2409          try {
2410            res = hostServices.resolveReference(this, s);
2411          } catch (Exception e) {
2412            res = null;
2413          }
2414        }
2415        if (res != null) {
2416          result.add(res);
2417        }
2418      }
2419    }
2420
2421    return result;
2422  }
2423
2424        private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2425    List<Base> result = new ArrayList<Base>();
2426    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2427    String url = nl.get(0).primitiveValue();
2428
2429    for (Base item : focus) {
2430      List<Base> ext = new ArrayList<Base>();
2431      getChildrenByName(item, "extension", ext);
2432      getChildrenByName(item, "modifierExtension", ext);
2433      for (Base ex : ext) {
2434        List<Base> vl = new ArrayList<Base>();
2435        getChildrenByName(ex, "url", vl);
2436        if (convertToString(vl).equals(url))
2437          result.add(ex);
2438      }
2439    }
2440    return result;
2441  }
2442
2443        private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2444    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2445    String name = nl.get(0).primitiveValue();
2446
2447    log(name, focus);
2448    return focus;
2449  }
2450
2451  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2452    if (focus.size() <= 1)
2453      return focus;
2454
2455    List<Base> result = new ArrayList<Base>();
2456    for (int i = 0; i < focus.size(); i++) {
2457      boolean found = false;
2458      for (int j = i+1; j < focus.size(); j++) {
2459        if (doEquals(focus.get(j), focus.get(i))) {
2460          found = true;
2461          break;
2462        }
2463      }
2464      if (!found)
2465        result.add(focus.get(i));
2466    }
2467    return result;
2468  }
2469
2470        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2471    List<Base> result = new ArrayList<Base>();
2472    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2473
2474    if (focus.size() == 1 && !Utilities.noString(sw)) {
2475      String st = convertToString(focus.get(0));
2476      if (Utilities.noString(st))
2477        result.add(new BooleanType(false));
2478      else
2479        result.add(new BooleanType(st.matches(sw)));
2480    } else
2481      result.add(new BooleanType(false));
2482    return result;
2483  }
2484
2485        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2486    List<Base> result = new ArrayList<Base>();
2487    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2488
2489    if (focus.size() == 1 && !Utilities.noString(sw)) {
2490      String st = convertToString(focus.get(0));
2491      if (Utilities.noString(st))
2492        result.add(new BooleanType(false));
2493      else
2494        result.add(new BooleanType(st.contains(sw)));
2495    }  else
2496      result.add(new BooleanType(false));
2497    return result;
2498  }
2499
2500  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2501    List<Base> result = new ArrayList<Base>();
2502    if (focus.size() == 1) {
2503      String s = convertToString(focus.get(0));
2504      result.add(new IntegerType(s.length()));
2505    }
2506    return result;
2507  }
2508
2509  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2510    List<Base> result = new ArrayList<Base>();
2511    if (focus.size() == 1) {
2512      String s = convertToString(focus.get(0));
2513      result.add(new BooleanType(!Utilities.noString(s)));
2514    }
2515    return result;
2516  }
2517
2518        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2519    List<Base> result = new ArrayList<Base>();
2520    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2521
2522    if (focus.size() == 1 && !Utilities.noString(sw))
2523      result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw)));
2524    else
2525      result.add(new BooleanType(false));
2526    return result;
2527  }
2528
2529        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2530    List<Base> result = new ArrayList<Base>();
2531    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2532    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2533    int i2 = -1;
2534    if (exp.parameterCount() == 2) {
2535      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
2536      i2 = Integer.parseInt(n2.get(0).primitiveValue());
2537    }
2538
2539    if (focus.size() == 1) {
2540      String sw = convertToString(focus.get(0));
2541      String s;
2542      if (i1 < 0 || i1 >= sw.length())
2543        return new ArrayList<Base>();
2544      if (exp.parameterCount() == 2)
2545        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
2546      else
2547        s = sw.substring(i1);
2548      if (!Utilities.noString(s))
2549        result.add(new StringType(s));
2550    }
2551    return result;
2552  }
2553
2554  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2555    String s = convertToString(focus);
2556    List<Base> result = new ArrayList<Base>();
2557    if (Utilities.isInteger(s))
2558      result.add(new IntegerType(s));
2559    return result;
2560  }
2561
2562  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2563    List<Base> result = new ArrayList<Base>();
2564    result.add(new IntegerType(focus.size()));
2565    return result;
2566  }
2567
2568  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2569    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2570    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2571
2572    List<Base> result = new ArrayList<Base>();
2573    for (int i = i1; i < focus.size(); i++)
2574      result.add(focus.get(i));
2575    return result;
2576  }
2577
2578  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2579    List<Base> result = new ArrayList<Base>();
2580    for (int i = 1; i < focus.size(); i++)
2581      result.add(focus.get(i));
2582    return result;
2583  }
2584
2585  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2586    List<Base> result = new ArrayList<Base>();
2587    if (focus.size() > 0)
2588      result.add(focus.get(focus.size()-1));
2589    return result;
2590  }
2591
2592  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2593    List<Base> result = new ArrayList<Base>();
2594    if (focus.size() > 0)
2595      result.add(focus.get(0));
2596    return result;
2597  }
2598
2599
2600        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2601    List<Base> result = new ArrayList<Base>();
2602    List<Base> pc = new ArrayList<Base>();
2603    for (Base item : focus) {
2604      pc.clear();
2605      pc.add(item);
2606      if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)))
2607        result.add(item);
2608    }
2609    return result;
2610  }
2611
2612  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2613    List<Base> result = new ArrayList<Base>();
2614    List<Base> pc = new ArrayList<Base>();
2615    for (Base item : focus) {
2616      pc.clear();
2617      pc.add(item);
2618      result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
2619    }
2620    return result;
2621  }
2622
2623
2624        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2625    List<Base> result = new ArrayList<Base>();
2626    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2627    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size())
2628      result.add(focus.get(Integer.parseInt(s)));
2629    return result;
2630  }
2631
2632  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2633    List<Base> result = new ArrayList<Base>();
2634                result.add(new BooleanType(ElementUtil.isEmpty(focus)));
2635    return result;
2636  }
2637
2638  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2639    return makeBoolean(!convertToBoolean(focus));
2640  }
2641
2642  private class ElementDefinitionMatch {
2643    private ElementDefinition definition;
2644    private String fixedType;
2645    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
2646      super();
2647      this.definition = definition;
2648      this.fixedType = fixedType;
2649    }
2650    public ElementDefinition getDefinition() {
2651      return definition;
2652    }
2653    public String getFixedType() {
2654      return fixedType;
2655    }
2656
2657  }
2658
2659  private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException {
2660    if (Utilities.noString(type))
2661      throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName");
2662    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml"))
2663      return;
2664    String url = null;
2665    if (type.contains("#")) {
2666      url = type.substring(0, type.indexOf("#"));
2667    } else {
2668      url = type;
2669    }
2670    String tail = "";
2671    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
2672    if (sd == null)
2673      throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong
2674    List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
2675    ElementDefinitionMatch m = null;
2676    if (type.contains("#"))
2677      m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false);
2678    if (m != null && hasDataType(m.definition)) {
2679      if (m.fixedType != null)
2680      {
2681        StructureDefinition dt = worker.fetchTypeDefinition(m.fixedType);
2682        if (dt == null)
2683          throw new DefinitionException("unknown data type "+m.fixedType);
2684        sdl.add(dt);
2685      } else
2686        for (TypeRefComponent t : m.definition.getType()) {
2687          StructureDefinition dt = worker.fetchTypeDefinition(t.getCode());
2688          if (dt == null)
2689            throw new DefinitionException("unknown data type "+t.getCode());
2690          sdl.add(dt);
2691        }
2692    } else {
2693      sdl.add(sd);
2694      if (type.contains("#")) {
2695        tail = type.substring(type.indexOf("#")+1);
2696        tail = tail.substring(tail.indexOf("."));
2697      }
2698    }
2699
2700    for (StructureDefinition sdi : sdl) {
2701      String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
2702      if (name.equals("**")) {
2703        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2704        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2705          if (ed.getPath().startsWith(path))
2706            for (TypeRefComponent t : ed.getType()) {
2707              if (t.hasCode() && t.getCodeElement().hasValue()) {
2708                String tn = null;
2709                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2710                  tn = sdi.getType()+"#"+ed.getPath();
2711                else
2712                  tn = t.getCode();
2713                if (t.getCode().equals("Resource")) {
2714                  for (String rn : worker.getResourceNames()) {
2715                    if (!result.hasType(worker, rn)) {
2716                      getChildTypesByName(result.addType(rn), "**", result);
2717                    }
2718                  }
2719                } else if (!result.hasType(worker, tn)) {
2720                  getChildTypesByName(result.addType(tn), "**", result);
2721                }
2722              }
2723            }
2724        }
2725      } else if (name.equals("*")) {
2726        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2727        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2728          if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
2729            for (TypeRefComponent t : ed.getType()) {
2730              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2731                result.addType(sdi.getType()+"#"+ed.getPath());
2732              else if (t.getCode().equals("Resource"))
2733                result.addTypes(worker.getResourceNames());
2734              else
2735                result.addType(t.getCode());
2736            }
2737        }
2738      } else {
2739        path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
2740
2741        ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
2742        if (ed != null) {
2743          if (!Utilities.noString(ed.getFixedType()))
2744            result.addType(ed.getFixedType());
2745          else
2746            for (TypeRefComponent t : ed.getDefinition().getType()) {
2747              if (Utilities.noString(t.getCode()))
2748                break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
2749
2750              ProfiledType pt = null;
2751              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2752                pt = new ProfiledType(sdi.getUrl()+"#"+path);
2753              else if (t.getCode().equals("Resource"))
2754                result.addTypes(worker.getResourceNames());
2755              else
2756                pt = new ProfiledType(t.getCode());
2757              if (pt != null) {
2758                if (t.hasProfile())
2759                  pt.addProfile(t.getProfile());
2760                if (ed.getDefinition().hasBinding())
2761                  pt.addBinding(ed.getDefinition().getBinding());
2762                result.addType(pt);
2763              }
2764            }
2765        }
2766      }
2767    }
2768  }
2769
2770  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException {
2771    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2772      if (ed.getPath().equals(path)) {
2773        if (ed.hasContentReference()) {
2774          return getElementDefinitionById(sd, ed.getContentReference());
2775        } else
2776          return new ElementDefinitionMatch(ed, null);
2777      }
2778      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3)
2779        return new ElementDefinitionMatch(ed, null);
2780      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
2781        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
2782        if (primitiveTypes.contains(s))
2783          return new ElementDefinitionMatch(ed, s);
2784        else
2785        return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
2786      }
2787      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) {
2788        // now we walk into the type.
2789        if (ed.getType().size() > 1)  // if there's more than one type, the test above would fail this
2790          throw new PathEngineException("Internal typing issue....");
2791        StructureDefinition nsd = worker.fetchTypeDefinition(ed.getType().get(0).getCode());
2792            if (nsd == null)
2793              throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode());
2794        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName);
2795      }
2796      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
2797        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
2798        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName);
2799      }
2800    }
2801    return null;
2802  }
2803
2804  private boolean isAbstractType(List<TypeRefComponent> list) {
2805        return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
2806}
2807
2808
2809  private boolean hasType(ElementDefinition ed, String s) {
2810    for (TypeRefComponent t : ed.getType())
2811      if (s.equalsIgnoreCase(t.getCode()))
2812        return true;
2813    return false;
2814  }
2815
2816  private boolean hasDataType(ElementDefinition ed) {
2817    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
2818  }
2819
2820  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
2821    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2822      if (ref.equals("#"+ed.getId()))
2823        return new ElementDefinitionMatch(ed, null);
2824    }
2825    return null;
2826  }
2827
2828
2829  public boolean hasLog() {
2830    return log != null && log.length() > 0;
2831  }
2832
2833
2834  public String takeLog() {
2835    if (!hasLog())
2836      return "";
2837    String s = log.toString();
2838    log = new StringBuilder();
2839    return s;
2840  }
2841
2842}