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