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 regex = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2248    String repl = convertToString(execute(context, focus, exp.getParameters().get(1), true));
2249
2250    if (focus.size() == 1 && !Utilities.noString(regex)) {
2251      result.add(new StringType(convertToString(focus.get(0)).replaceAll(regex, repl)));
2252    } else {
2253      result.add(new StringType(convertToString(focus.get(0))));
2254    }
2255    return result;
2256  }
2257
2258
2259  private List<Base> funcEndsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2260    List<Base> result = new ArrayList<Base>();
2261    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2262
2263    if (focus.size() == 1 && !Utilities.noString(sw))
2264      result.add(new BooleanType(convertToString(focus.get(0)).endsWith(sw)));
2265    else
2266      result.add(new BooleanType(false));
2267    return result;
2268  }
2269
2270
2271  private List<Base> funcToString(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2272    List<Base> result = new ArrayList<Base>();
2273    result.add(new StringType(convertToString(focus)));
2274    return result;
2275  }
2276
2277
2278  private List<Base> funcToDecimal(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2279    String s = convertToString(focus);
2280    List<Base> result = new ArrayList<Base>();
2281    if (Utilities.isDecimal(s, true))
2282      result.add(new DecimalType(s));
2283    return result;
2284  }
2285
2286
2287  private List<Base> funcIif(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2288    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2289    Boolean v = convertToBoolean(n1);
2290
2291    if (v)
2292      return execute(context, focus, exp.getParameters().get(1), true);
2293    else if (exp.getParameters().size() < 3)
2294      return new ArrayList<Base>();
2295    else
2296      return execute(context, focus, exp.getParameters().get(2), true);
2297  }
2298
2299
2300  private List<Base> funcTake(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2301    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2302    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2303
2304    List<Base> result = new ArrayList<Base>();
2305    for (int i = 0; i < Math.min(focus.size(), i1); i++)
2306      result.add(focus.get(i));
2307    return result;
2308  }
2309
2310
2311  private List<Base> funcSingle(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2312    if (focus.size() == 1)
2313      return focus;
2314    throw new PathEngineException(String.format("Single() : checking for 1 item but found %d items", focus.size()));
2315  }
2316
2317
2318  private List<Base> funcIs(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws PathEngineException {
2319    List<Base> result = new ArrayList<Base>();
2320    if (focus.size() == 0 || focus.size() > 1)
2321      result.add(new BooleanType(false));
2322    else {
2323      String tn = exp.getParameters().get(0).getName();
2324      result.add(new BooleanType(focus.get(0).hasType(tn)));
2325    }
2326    return result;
2327  }
2328
2329
2330  private List<Base> funcAs(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2331    List<Base> result = new ArrayList<Base>();
2332    String tn = exp.getParameters().get(0).getName();
2333    for (Base b : focus)
2334      if (b.hasType(tn))
2335        result.add(b);
2336    return result;
2337  }
2338
2339
2340  private List<Base> funcRepeat(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2341    List<Base> result = new ArrayList<Base>();
2342    List<Base> current = new ArrayList<Base>();
2343    current.addAll(focus);
2344    List<Base> added = new ArrayList<Base>();
2345    boolean more = true;
2346    while (more) {
2347      added.clear();
2348      List<Base> pc = new ArrayList<Base>();
2349      for (Base item : current) {
2350        pc.clear();
2351        pc.add(item);
2352        added.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), false));
2353      }
2354      more = !added.isEmpty();
2355      result.addAll(added);
2356      current.clear();
2357      current.addAll(added);
2358    }
2359    return result;
2360  }
2361
2362
2363
2364  private List<Base> funcIsDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2365    if (focus.size() <= 1)
2366      return makeBoolean(true);
2367
2368    boolean distinct = true;
2369    for (int i = 0; i < focus.size(); i++) {
2370      for (int j = i+1; j < focus.size(); j++) {
2371        if (doEquals(focus.get(j), focus.get(i))) {
2372          distinct = false;
2373          break;
2374        }
2375      }
2376    }
2377    return makeBoolean(distinct);
2378  }
2379
2380
2381  private List<Base> funcSupersetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2382    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2383
2384    boolean valid = true;
2385    for (Base item : target) {
2386      boolean found = false;
2387      for (Base t : focus) {
2388        if (Base.compareDeep(item, t, false)) {
2389          found = true;
2390          break;
2391        }
2392      }
2393      if (!found) {
2394        valid = false;
2395        break;
2396      }
2397    }
2398    List<Base> result = new ArrayList<Base>();
2399    result.add(new BooleanType(valid));
2400    return result;
2401  }
2402
2403
2404  private List<Base> funcSubsetOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2405    List<Base> target = execute(context, focus, exp.getParameters().get(0), true);
2406
2407    boolean valid = true;
2408    for (Base item : focus) {
2409      boolean found = false;
2410      for (Base t : target) {
2411        if (Base.compareDeep(item, t, false)) {
2412          found = true;
2413          break;
2414        }
2415      }
2416      if (!found) {
2417        valid = false;
2418        break;
2419      }
2420    }
2421    List<Base> result = new ArrayList<Base>();
2422    result.add(new BooleanType(valid));
2423    return result;
2424  }
2425
2426
2427  private List<Base> funcExists(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2428    List<Base> result = new ArrayList<Base>();
2429    result.add(new BooleanType(!ElementUtil.isEmpty(focus)));
2430    return result;
2431  }
2432
2433
2434  private List<Base> funcResolve(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2435    List<Base> result = new ArrayList<Base>();
2436    for (Base item : focus) {
2437        String s = convertToString(item);
2438        if (item.fhirType().equals("Reference")) {
2439          Property p = item.getChildByName("reference");
2440        if (p.hasValues())
2441            s = convertToString(p.getValues().get(0));
2442        }
2443        Base res = null;
2444        if (s.startsWith("#")) {
2445          Property p = context.resource.getChildByName("contained");
2446          for (Base c : p.getValues()) {
2447          if (s.equals(c.getIdBase()))
2448              res = c;
2449        }
2450      } else if (hostServices != null) {
2451         res = hostServices.resolveReference(context.appInfo, s);
2452      }
2453        if (res != null)
2454          result.add(res);
2455      }
2456    return result;
2457  }
2458
2459        private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2460    List<Base> result = new ArrayList<Base>();
2461    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2462    String url = nl.get(0).primitiveValue();
2463
2464    for (Base item : focus) {
2465      List<Base> ext = new ArrayList<Base>();
2466      getChildrenByName(item, "extension", ext);
2467      getChildrenByName(item, "modifierExtension", ext);
2468      for (Base ex : ext) {
2469        List<Base> vl = new ArrayList<Base>();
2470        getChildrenByName(ex, "url", vl);
2471        if (convertToString(vl).equals(url))
2472          result.add(ex);
2473      }
2474    }
2475    return result;
2476  }
2477
2478        private List<Base> funcTrace(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2479    List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
2480    String name = nl.get(0).primitiveValue();
2481
2482    log(name, focus);
2483    return focus;
2484  }
2485
2486  private List<Base> funcDistinct(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2487    if (focus.size() <= 1)
2488      return focus;
2489
2490    List<Base> result = new ArrayList<Base>();
2491    for (int i = 0; i < focus.size(); i++) {
2492      boolean found = false;
2493      for (int j = i+1; j < focus.size(); j++) {
2494        if (doEquals(focus.get(j), focus.get(i))) {
2495          found = true;
2496          break;
2497        }
2498      }
2499      if (!found)
2500        result.add(focus.get(i));
2501    }
2502    return result;
2503  }
2504
2505        private List<Base> funcMatches(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2506    List<Base> result = new ArrayList<Base>();
2507    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2508
2509    if (focus.size() == 1 && !Utilities.noString(sw)) {
2510      String st = convertToString(focus.get(0));
2511      if (Utilities.noString(st))
2512        result.add(new BooleanType(false));
2513      else
2514        result.add(new BooleanType(st.matches(sw)));
2515    } else
2516      result.add(new BooleanType(false));
2517    return result;
2518  }
2519
2520        private List<Base> funcContains(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2521    List<Base> result = new ArrayList<Base>();
2522    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2523
2524    if (focus.size() == 1 && !Utilities.noString(sw)) {
2525      String st = convertToString(focus.get(0));
2526      if (Utilities.noString(st))
2527        result.add(new BooleanType(false));
2528      else
2529        result.add(new BooleanType(st.contains(sw)));
2530    }  else
2531      result.add(new BooleanType(false));
2532    return result;
2533  }
2534
2535  private List<Base> funcLength(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2536    List<Base> result = new ArrayList<Base>();
2537    if (focus.size() == 1) {
2538      String s = convertToString(focus.get(0));
2539      result.add(new IntegerType(s.length()));
2540    }
2541    return result;
2542  }
2543
2544  private List<Base> funcHasValue(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2545    List<Base> result = new ArrayList<Base>();
2546    if (focus.size() == 1) {
2547      String s = convertToString(focus.get(0));
2548      result.add(new BooleanType(!Utilities.noString(s)));
2549    }
2550    return result;
2551  }
2552
2553        private List<Base> funcStartsWith(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2554    List<Base> result = new ArrayList<Base>();
2555    String sw = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2556
2557    if (focus.size() == 1 && !Utilities.noString(sw))
2558      result.add(new BooleanType(convertToString(focus.get(0)).startsWith(sw)));
2559    else
2560      result.add(new BooleanType(false));
2561    return result;
2562  }
2563
2564        private List<Base> funcSubstring(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2565    List<Base> result = new ArrayList<Base>();
2566    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2567    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2568    int i2 = -1;
2569    if (exp.parameterCount() == 2) {
2570      List<Base> n2 = execute(context, focus, exp.getParameters().get(1), true);
2571      i2 = Integer.parseInt(n2.get(0).primitiveValue());
2572    }
2573
2574    if (focus.size() == 1) {
2575      String sw = convertToString(focus.get(0));
2576      String s;
2577      if (i1 < 0 || i1 >= sw.length())
2578        return new ArrayList<Base>();
2579      if (exp.parameterCount() == 2)
2580        s = sw.substring(i1, Math.min(sw.length(), i1+i2));
2581      else
2582        s = sw.substring(i1);
2583      if (!Utilities.noString(s))
2584        result.add(new StringType(s));
2585    }
2586    return result;
2587  }
2588
2589  private List<Base> funcToInteger(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2590    String s = convertToString(focus);
2591    List<Base> result = new ArrayList<Base>();
2592    if (Utilities.isInteger(s))
2593      result.add(new IntegerType(s));
2594    return result;
2595  }
2596
2597  private List<Base> funcCount(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2598    List<Base> result = new ArrayList<Base>();
2599    result.add(new IntegerType(focus.size()));
2600    return result;
2601  }
2602
2603  private List<Base> funcSkip(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2604    List<Base> n1 = execute(context, focus, exp.getParameters().get(0), true);
2605    int i1 = Integer.parseInt(n1.get(0).primitiveValue());
2606
2607    List<Base> result = new ArrayList<Base>();
2608    for (int i = i1; i < focus.size(); i++)
2609      result.add(focus.get(i));
2610    return result;
2611  }
2612
2613  private List<Base> funcTail(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2614    List<Base> result = new ArrayList<Base>();
2615    for (int i = 1; i < focus.size(); i++)
2616      result.add(focus.get(i));
2617    return result;
2618  }
2619
2620  private List<Base> funcLast(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2621    List<Base> result = new ArrayList<Base>();
2622    if (focus.size() > 0)
2623      result.add(focus.get(focus.size()-1));
2624    return result;
2625  }
2626
2627  private List<Base> funcFirst(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2628    List<Base> result = new ArrayList<Base>();
2629    if (focus.size() > 0)
2630      result.add(focus.get(0));
2631    return result;
2632  }
2633
2634
2635        private List<Base> funcWhere(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2636    List<Base> result = new ArrayList<Base>();
2637    List<Base> pc = new ArrayList<Base>();
2638    for (Base item : focus) {
2639      pc.clear();
2640      pc.add(item);
2641      if (convertToBoolean(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)))
2642        result.add(item);
2643    }
2644    return result;
2645  }
2646
2647  private List<Base> funcSelect(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2648    List<Base> result = new ArrayList<Base>();
2649    List<Base> pc = new ArrayList<Base>();
2650    for (Base item : focus) {
2651      pc.clear();
2652      pc.add(item);
2653      result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true));
2654    }
2655    return result;
2656  }
2657
2658
2659        private List<Base> funcItem(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
2660    List<Base> result = new ArrayList<Base>();
2661    String s = convertToString(execute(context, focus, exp.getParameters().get(0), true));
2662    if (Utilities.isInteger(s) && Integer.parseInt(s) < focus.size())
2663      result.add(focus.get(Integer.parseInt(s)));
2664    return result;
2665  }
2666
2667  private List<Base> funcEmpty(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2668    List<Base> result = new ArrayList<Base>();
2669                result.add(new BooleanType(ElementUtil.isEmpty(focus)));
2670    return result;
2671  }
2672
2673  private List<Base> funcNot(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
2674    return makeBoolean(!convertToBoolean(focus));
2675  }
2676
2677  public class ElementDefinitionMatch {
2678    private ElementDefinition definition;
2679    private String fixedType;
2680    public ElementDefinitionMatch(ElementDefinition definition, String fixedType) {
2681      super();
2682      this.definition = definition;
2683      this.fixedType = fixedType;
2684    }
2685    public ElementDefinition getDefinition() {
2686      return definition;
2687    }
2688    public String getFixedType() {
2689      return fixedType;
2690    }
2691
2692  }
2693
2694  private void getChildTypesByName(String type, String name, TypeDetails result) throws PathEngineException, DefinitionException {
2695    if (Utilities.noString(type))
2696      throw new PathEngineException("No type provided in BuildToolPathEvaluator.getChildTypesByName");
2697    if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml"))
2698      return;
2699    String url = null;
2700    if (type.contains("#")) {
2701      url = type.substring(0, type.indexOf("#"));
2702    } else {
2703      url = type;
2704    }
2705    String tail = "";
2706    StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
2707    if (sd == null)
2708      throw new DefinitionException("Unknown type "+type); // this really is an error, because we can only get to here if the internal infrastrucgture is wrong
2709    List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
2710    ElementDefinitionMatch m = null;
2711    if (type.contains("#"))
2712      m = getElementDefinition(sd, type.substring(type.indexOf("#")+1), false);
2713    if (m != null && hasDataType(m.definition)) {
2714      if (m.fixedType != null)
2715      {
2716        StructureDefinition dt = worker.fetchTypeDefinition(m.fixedType);
2717        if (dt == null)
2718          throw new DefinitionException("unknown data type "+m.fixedType);
2719        sdl.add(dt);
2720      } else
2721        for (TypeRefComponent t : m.definition.getType()) {
2722          StructureDefinition dt = worker.fetchTypeDefinition(t.getCode());
2723          if (dt == null)
2724            throw new DefinitionException("unknown data type "+t.getCode());
2725          sdl.add(dt);
2726        }
2727    } else {
2728      sdl.add(sd);
2729      if (type.contains("#")) {
2730        tail = type.substring(type.indexOf("#")+1);
2731        tail = tail.substring(tail.indexOf("."));
2732      }
2733    }
2734
2735    for (StructureDefinition sdi : sdl) {
2736      String path = sdi.getSnapshot().getElement().get(0).getPath()+tail+".";
2737      if (name.equals("**")) {
2738        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2739        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2740          if (ed.getPath().startsWith(path))
2741            for (TypeRefComponent t : ed.getType()) {
2742              if (t.hasCode() && t.getCodeElement().hasValue()) {
2743                String tn = null;
2744                if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2745                  tn = sdi.getType()+"#"+ed.getPath();
2746                else
2747                  tn = t.getCode();
2748                if (t.getCode().equals("Resource")) {
2749                  for (String rn : worker.getResourceNames()) {
2750                    if (!result.hasType(worker, rn)) {
2751                      getChildTypesByName(result.addType(rn), "**", result);
2752                    }
2753                  }
2754                } else if (!result.hasType(worker, tn)) {
2755                  getChildTypesByName(result.addType(tn), "**", result);
2756                }
2757              }
2758            }
2759        }
2760      } else if (name.equals("*")) {
2761        assert(result.getCollectionStatus() == CollectionStatus.UNORDERED);
2762        for (ElementDefinition ed : sdi.getSnapshot().getElement()) {
2763          if (ed.getPath().startsWith(path) && !ed.getPath().substring(path.length()).contains("."))
2764            for (TypeRefComponent t : ed.getType()) {
2765              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2766                result.addType(sdi.getType()+"#"+ed.getPath());
2767              else if (t.getCode().equals("Resource"))
2768                result.addTypes(worker.getResourceNames());
2769              else
2770                result.addType(t.getCode());
2771            }
2772        }
2773      } else {
2774        path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
2775
2776        ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
2777        if (ed != null) {
2778          if (!Utilities.noString(ed.getFixedType()))
2779            result.addType(ed.getFixedType());
2780          else
2781            for (TypeRefComponent t : ed.getDefinition().getType()) {
2782              if (Utilities.noString(t.getCode()))
2783                break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
2784
2785              ProfiledType pt = null;
2786              if (t.getCode().equals("Element") || t.getCode().equals("BackboneElement"))
2787                pt = new ProfiledType(sdi.getUrl()+"#"+path);
2788              else if (t.getCode().equals("Resource"))
2789                result.addTypes(worker.getResourceNames());
2790              else
2791                pt = new ProfiledType(t.getCode());
2792              if (pt != null) {
2793                if (t.hasProfile())
2794                  pt.addProfile(t.getProfile());
2795                if (ed.getDefinition().hasBinding())
2796                  pt.addBinding(ed.getDefinition().getBinding());
2797                result.addType(pt);
2798              }
2799            }
2800        }
2801      }
2802    }
2803  }
2804
2805  private ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName) throws PathEngineException {
2806    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2807      if (ed.getPath().equals(path)) {
2808        if (ed.hasContentReference()) {
2809          return getElementDefinitionById(sd, ed.getContentReference());
2810        } else
2811          return new ElementDefinitionMatch(ed, null);
2812      }
2813      if (ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() == ed.getPath().length()-3)
2814        return new ElementDefinitionMatch(ed, null);
2815      if (allowTypedName && ed.getPath().endsWith("[x]") && path.startsWith(ed.getPath().substring(0, ed.getPath().length()-3)) && path.length() > ed.getPath().length()-3) {
2816        String s = Utilities.uncapitalize(path.substring(ed.getPath().length()-3));
2817        if (primitiveTypes.contains(s))
2818          return new ElementDefinitionMatch(ed, s);
2819        else
2820        return new ElementDefinitionMatch(ed, path.substring(ed.getPath().length()-3));
2821      }
2822      if (ed.getPath().contains(".") && path.startsWith(ed.getPath()+".") && (ed.getType().size() > 0) && !isAbstractType(ed.getType())) {
2823        // now we walk into the type.
2824        if (ed.getType().size() > 1)  // if there's more than one type, the test above would fail this
2825          throw new PathEngineException("Internal typing issue....");
2826        StructureDefinition nsd = worker.fetchTypeDefinition(ed.getType().get(0).getCode());
2827            if (nsd == null)
2828              throw new PathEngineException("Unknown type "+ed.getType().get(0).getCode());
2829        return getElementDefinition(nsd, nsd.getId()+path.substring(ed.getPath().length()), allowTypedName);
2830      }
2831      if (ed.hasContentReference() && path.startsWith(ed.getPath()+".")) {
2832        ElementDefinitionMatch m = getElementDefinitionById(sd, ed.getContentReference());
2833        return getElementDefinition(sd, m.definition.getPath()+path.substring(ed.getPath().length()), allowTypedName);
2834      }
2835    }
2836    return null;
2837  }
2838
2839  private boolean isAbstractType(List<TypeRefComponent> list) {
2840        return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
2841}
2842
2843
2844  private boolean hasType(ElementDefinition ed, String s) {
2845    for (TypeRefComponent t : ed.getType())
2846      if (s.equalsIgnoreCase(t.getCode()))
2847        return true;
2848    return false;
2849  }
2850
2851  private boolean hasDataType(ElementDefinition ed) {
2852    return ed.hasType() && !(ed.getType().get(0).getCode().equals("Element") || ed.getType().get(0).getCode().equals("BackboneElement"));
2853  }
2854
2855  private ElementDefinitionMatch getElementDefinitionById(StructureDefinition sd, String ref) {
2856    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2857      if (ref.equals("#"+ed.getId()))
2858        return new ElementDefinitionMatch(ed, null);
2859    }
2860    return null;
2861  }
2862
2863
2864  public boolean hasLog() {
2865    return log != null && log.length() > 0;
2866  }
2867
2868
2869  public String takeLog() {
2870    if (!hasLog())
2871      return "";
2872    String s = log.toString();
2873    log = new StringBuilder();
2874    return s;
2875  }
2876
2877}