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