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