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