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