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