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