001package org.hl7.fhir.dstu2.model;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.HashSet;
035import java.util.List;
036import java.util.Set;
037
038import org.hl7.fhir.dstu2.utils.IWorkerContext;
039import org.hl7.fhir.utilities.Utilities;
040
041public class ExpressionNode {
042
043  public enum Kind {
044    Name, Function, Constant, Group
045  }
046
047  public static class SourceLocation {
048    private int line;
049    private int column;
050
051    public SourceLocation(int line, int column) {
052      super();
053      this.line = line;
054      this.column = column;
055    }
056
057    public int getLine() {
058      return line;
059    }
060
061    public int getColumn() {
062      return column;
063    }
064
065    public void setLine(int line) {
066      this.line = line;
067    }
068
069    public void setColumn(int column) {
070      this.column = column;
071    }
072
073    public String toString() {
074      return Integer.toString(line) + ", " + Integer.toString(column);
075    }
076    public SourceLocation copy() {
077      return new SourceLocation(line, column);
078    }
079    
080    public void incColumn() {
081      incColumn(1);    
082    }
083    public void incColumn(int i) {
084      column = column + i;
085      
086    }
087  }
088
089  public enum Function {
090    Custom,
091
092    Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat,
093    Item /* implicit from name[] */, As, Is, Single, First, Last, Tail, Skip, Take, Iif, ToInteger, ToDecimal, ToString,
094    Substring, StartsWith, EndsWith, Matches, ReplaceMatches, Contains, Replace, Length, Children, Descendants,
095    MemberOf, Trace, Today, Now, Resolve, Extension;
096
097    public static Function fromCode(String name) {
098      if (name.equals("empty"))
099        return Function.Empty;
100      if (name.equals("not"))
101        return Function.Not;
102      if (name.equals("exists"))
103        return Function.Exists;
104      if (name.equals("subsetOf"))
105        return Function.SubsetOf;
106      if (name.equals("supersetOf"))
107        return Function.SupersetOf;
108      if (name.equals("isDistinct"))
109        return Function.IsDistinct;
110      if (name.equals("distinct"))
111        return Function.Distinct;
112      if (name.equals("count"))
113        return Function.Count;
114      if (name.equals("where"))
115        return Function.Where;
116      if (name.equals("select"))
117        return Function.Select;
118      if (name.equals("all"))
119        return Function.All;
120      if (name.equals("repeat"))
121        return Function.Repeat;
122      if (name.equals("item"))
123        return Function.Item;
124      if (name.equals("as"))
125        return Function.As;
126      if (name.equals("is"))
127        return Function.Is;
128      if (name.equals("single"))
129        return Function.Single;
130      if (name.equals("first"))
131        return Function.First;
132      if (name.equals("last"))
133        return Function.Last;
134      if (name.equals("tail"))
135        return Function.Tail;
136      if (name.equals("skip"))
137        return Function.Skip;
138      if (name.equals("take"))
139        return Function.Take;
140      if (name.equals("iif"))
141        return Function.Iif;
142      if (name.equals("toInteger"))
143        return Function.ToInteger;
144      if (name.equals("toDecimal"))
145        return Function.ToDecimal;
146      if (name.equals("toString"))
147        return Function.ToString;
148      if (name.equals("substring"))
149        return Function.Substring;
150      if (name.equals("startsWith"))
151        return Function.StartsWith;
152      if (name.equals("endsWith"))
153        return Function.EndsWith;
154      if (name.equals("matches"))
155        return Function.Matches;
156      if (name.equals("replaceMatches"))
157        return Function.ReplaceMatches;
158      if (name.equals("contains"))
159        return Function.Contains;
160      if (name.equals("replace"))
161        return Function.Replace;
162      if (name.equals("length"))
163        return Function.Length;
164      if (name.equals("children"))
165        return Function.Children;
166      if (name.equals("descendants"))
167        return Function.Descendants;
168      if (name.equals("memberOf"))
169        return Function.MemberOf;
170      if (name.equals("trace"))
171        return Function.Trace;
172      if (name.equals("today"))
173        return Function.Today;
174      if (name.equals("now"))
175        return Function.Now;
176      if (name.equals("resolve"))
177        return Function.Resolve;
178      if (name.equals("extension"))
179        return Function.Extension;
180      return null;
181    }
182
183    public String toCode() {
184      switch (this) {
185      case Empty:
186        return "empty";
187      case Not:
188        return "not";
189      case Exists:
190        return "exists";
191      case SubsetOf:
192        return "subsetOf";
193      case SupersetOf:
194        return "supersetOf";
195      case IsDistinct:
196        return "isDistinct";
197      case Distinct:
198        return "distinct";
199      case Count:
200        return "count";
201      case Where:
202        return "where";
203      case Select:
204        return "select";
205      case All:
206        return "all";
207      case Repeat:
208        return "repeat";
209      case Item:
210        return "item";
211      case As:
212        return "as";
213      case Is:
214        return "is";
215      case Single:
216        return "single";
217      case First:
218        return "first";
219      case Last:
220        return "last";
221      case Tail:
222        return "tail";
223      case Skip:
224        return "skip";
225      case Take:
226        return "take";
227      case Iif:
228        return "iif";
229      case ToInteger:
230        return "toInteger";
231      case ToDecimal:
232        return "toDecimal";
233      case ToString:
234        return "toString";
235      case Substring:
236        return "substring";
237      case StartsWith:
238        return "startsWith";
239      case EndsWith:
240        return "endsWith";
241      case Matches:
242        return "matches";
243      case ReplaceMatches:
244        return "replaceMatches";
245      case Contains:
246        return "contains";
247      case Replace:
248        return "replace";
249      case Length:
250        return "length";
251      case Children:
252        return "children";
253      case Descendants:
254        return "descendants";
255      case MemberOf:
256        return "memberOf";
257      case Trace:
258        return "trace";
259      case Today:
260        return "today";
261      case Now:
262        return "now";
263      case Resolve:
264        return "resolve";
265      case Extension:
266        return "extension";
267      default:
268        return "??";
269      }
270    }
271  }
272
273  public enum Operation {
274    Equals, Equivalent, NotEquals, NotEquivalent, LessThen, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or,
275    And, Xor, Implies, Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains;
276
277    public static Operation fromCode(String name) {
278      if (Utilities.noString(name))
279        return null;
280      if (name.equals("="))
281        return Operation.Equals;
282      if (name.equals("~"))
283        return Operation.Equivalent;
284      if (name.equals("!="))
285        return Operation.NotEquals;
286      if (name.equals("!~"))
287        return Operation.NotEquivalent;
288      if (name.equals(">"))
289        return Operation.Greater;
290      if (name.equals("<"))
291        return Operation.LessThen;
292      if (name.equals(">="))
293        return Operation.GreaterOrEqual;
294      if (name.equals("<="))
295        return Operation.LessOrEqual;
296      if (name.equals("|"))
297        return Operation.Union;
298      if (name.equals("or"))
299        return Operation.Or;
300      if (name.equals("and"))
301        return Operation.And;
302      if (name.equals("xor"))
303        return Operation.Xor;
304      if (name.equals("is"))
305        return Operation.Is;
306      if (name.equals("as"))
307        return Operation.As;
308      if (name.equals("*"))
309        return Operation.Times;
310      if (name.equals("/"))
311        return Operation.DivideBy;
312      if (name.equals("+"))
313        return Operation.Plus;
314      if (name.equals("-"))
315        return Operation.Minus;
316      if (name.equals("&"))
317        return Operation.Concatenate;
318      if (name.equals("implies"))
319        return Operation.Implies;
320      if (name.equals("div"))
321        return Operation.Div;
322      if (name.equals("mod"))
323        return Operation.Mod;
324      if (name.equals("in"))
325        return Operation.In;
326      if (name.equals("contains"))
327        return Operation.Contains;
328      return null;
329
330    }
331
332    public String toCode() {
333      switch (this) {
334      case Equals:
335        return "=";
336      case Equivalent:
337        return "~";
338      case NotEquals:
339        return "!=";
340      case NotEquivalent:
341        return "!~";
342      case Greater:
343        return ">";
344      case LessThen:
345        return "<";
346      case GreaterOrEqual:
347        return ">=";
348      case LessOrEqual:
349        return "<=";
350      case Union:
351        return "|";
352      case Or:
353        return "or";
354      case And:
355        return "and";
356      case Xor:
357        return "xor";
358      case Times:
359        return "*";
360      case DivideBy:
361        return "/";
362      case Plus:
363        return "+";
364      case Minus:
365        return "-";
366      case Concatenate:
367        return "&";
368      case Implies:
369        return "implies";
370      case Is:
371        return "is";
372      case As:
373        return "as";
374      case Div:
375        return "div";
376      case Mod:
377        return "mod";
378      case In:
379        return "in";
380      case Contains:
381        return "contains";
382      default:
383        return "??";
384      }
385    }
386  }
387
388  public enum CollectionStatus {
389    SINGLETON, ORDERED, UNORDERED
390  }
391
392  public static class TypeDetails {
393    @Override
394    public String toString() {
395      return (collectionStatus == null ? "" : collectionStatus.toString()) + (types == null ? "[]" : types.toString());
396    }
397
398    private Set<String> types = new HashSet<String>();
399    private CollectionStatus collectionStatus;
400
401    public TypeDetails(CollectionStatus collectionStatus, String... names) {
402      super();
403      this.collectionStatus = collectionStatus;
404      for (String n : names)
405        this.types.add(n);
406    }
407
408    public TypeDetails(CollectionStatus collectionStatus, Set<String> names) {
409      super();
410      this.collectionStatus = collectionStatus;
411      for (String n : names)
412        this.types.add(n);
413    }
414
415    public void addType(String n) {
416      this.types.add(n);
417    }
418
419    public void addTypes(Collection<String> n) {
420      this.types.addAll(n);
421    }
422
423    public boolean hasType(IWorkerContext context, String... tn) {
424      for (String t : tn)
425        if (types.contains(t))
426          return true;
427      for (String t : tn) {
428        StructureDefinition sd = context.fetchTypeDefinition(t);
429        while (sd != null) {
430          if (types.contains(sd.getId()))
431            return true;
432          if (sd.hasBase())
433            sd = context.fetchResource(StructureDefinition.class, sd.getBase());
434          else
435            sd = null;
436        }
437      }
438      return false;
439    }
440
441    public void update(TypeDetails source) {
442      types.addAll(source.types);
443      if (collectionStatus == null)
444        collectionStatus = source.collectionStatus;
445      else if (source.collectionStatus == CollectionStatus.UNORDERED)
446        collectionStatus = source.collectionStatus;
447      else
448        collectionStatus = CollectionStatus.ORDERED;
449    }
450
451    public TypeDetails union(TypeDetails right) {
452      TypeDetails result = new TypeDetails(null);
453      if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
454        result.collectionStatus = CollectionStatus.UNORDERED;
455      else
456        result.collectionStatus = CollectionStatus.ORDERED;
457      result.types.addAll(types);
458      result.types.addAll(right.types);
459      return result;
460    }
461
462    public boolean hasNoTypes() {
463      return types.isEmpty();
464    }
465
466    public Set<String> getTypes() {
467      return types;
468    }
469
470    public TypeDetails toSingleton() {
471      TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
472      result.types.addAll(types);
473      return result;
474    }
475
476    public CollectionStatus getCollectionStatus() {
477      return collectionStatus;
478    }
479
480    public boolean hasType(Set<String> tn) {
481      for (String t : tn)
482        if (types.contains(t))
483          return true;
484      return false;
485    }
486
487    public String describe() {
488      return types.toString();
489    }
490
491    public String getType() {
492      for (String t : types)
493        return t;
494      return null;
495    }
496
497  }
498
499  // the expression will have one of either name or constant
500  private String uniqueId;
501  private Kind kind;
502  private String name;
503  private String constant;
504  private Function function;
505  private List<ExpressionNode> parameters; // will be created if there is a function
506  private ExpressionNode inner;
507  private ExpressionNode group;
508  private Operation operation;
509  private boolean proximal; // a proximal operation is the first in the sequence of operations. This is
510                            // significant when evaluating the outcomes
511  private ExpressionNode opNext;
512  private SourceLocation start;
513  private SourceLocation end;
514  private SourceLocation opStart;
515  private SourceLocation opEnd;
516  private TypeDetails types;
517  private TypeDetails opTypes;
518
519  public ExpressionNode(int uniqueId) {
520    super();
521    this.uniqueId = Integer.toString(uniqueId);
522  }
523
524  public String toString() {
525    StringBuilder b = new StringBuilder();
526    switch (kind) {
527    case Name:
528      b.append(name);
529      break;
530    case Function:
531      if (function == Function.Item)
532        b.append("[");
533      else {
534        b.append(name);
535        b.append("(");
536      }
537      boolean first = true;
538      for (ExpressionNode n : parameters) {
539        if (first)
540          first = false;
541        else
542          b.append(", ");
543        b.append(n.toString());
544      }
545      if (function == Function.Item)
546        b.append("]");
547      else {
548        b.append(")");
549      }
550      break;
551    case Constant:
552      b.append(Utilities.escapeJava(constant));
553      break;
554    case Group:
555      b.append("(");
556      b.append(group.toString());
557      b.append(")");
558    }
559    if (inner != null) {
560      b.append(".");
561      b.append(inner.toString());
562    }
563    if (operation != null) {
564      b.append(" ");
565      b.append(operation.toCode());
566      b.append(" ");
567      b.append(opNext.toString());
568    }
569
570    return b.toString();
571  }
572
573  public String getName() {
574    return name;
575  }
576
577  public void setName(String name) {
578    this.name = name;
579  }
580
581  public String getConstant() {
582    return constant;
583  }
584
585  public void setConstant(String constant) {
586    this.constant = constant;
587  }
588
589  public Function getFunction() {
590    return function;
591  }
592
593  public void setFunction(Function function) {
594    this.function = function;
595    if (parameters == null)
596      parameters = new ArrayList<ExpressionNode>();
597  }
598
599  public boolean isProximal() {
600    return proximal;
601  }
602
603  public void setProximal(boolean proximal) {
604    this.proximal = proximal;
605  }
606
607  public Operation getOperation() {
608    return operation;
609  }
610
611  public void setOperation(Operation operation) {
612    this.operation = operation;
613  }
614
615  public ExpressionNode getInner() {
616    return inner;
617  }
618
619  public void setInner(ExpressionNode value) {
620    this.inner = value;
621  }
622
623  public ExpressionNode getOpNext() {
624    return opNext;
625  }
626
627  public void setOpNext(ExpressionNode value) {
628    this.opNext = value;
629  }
630
631  public List<ExpressionNode> getParameters() {
632    return parameters;
633  }
634
635  public boolean checkName() {
636    if (!name.startsWith("$"))
637      return true;
638    else
639      return name.equals("$this");
640  }
641
642  public Kind getKind() {
643    return kind;
644  }
645
646  public void setKind(Kind kind) {
647    this.kind = kind;
648  }
649
650  public ExpressionNode getGroup() {
651    return group;
652  }
653
654  public void setGroup(ExpressionNode group) {
655    this.group = group;
656  }
657
658  public SourceLocation getStart() {
659    return start;
660  }
661
662  public void setStart(SourceLocation start) {
663    this.start = start;
664  }
665
666  public SourceLocation getEnd() {
667    return end;
668  }
669
670  public void setEnd(SourceLocation end) {
671    this.end = end;
672  }
673
674  public SourceLocation getOpStart() {
675    return opStart;
676  }
677
678  public void setOpStart(SourceLocation opStart) {
679    this.opStart = opStart;
680  }
681
682  public SourceLocation getOpEnd() {
683    return opEnd;
684  }
685
686  public void setOpEnd(SourceLocation opEnd) {
687    this.opEnd = opEnd;
688  }
689
690  public String getUniqueId() {
691    return uniqueId;
692  }
693
694  public int parameterCount() {
695    if (parameters == null)
696      return 0;
697    else
698      return parameters.size();
699  }
700
701  public String Canonical() {
702    StringBuilder b = new StringBuilder();
703    write(b);
704    return b.toString();
705  }
706
707  public String summary() {
708    switch (kind) {
709    case Name:
710      return uniqueId + ": " + name;
711    case Function:
712      return uniqueId + ": " + function.toString() + "()";
713    case Constant:
714      return uniqueId + ": " + constant;
715    case Group:
716      return uniqueId + ": (Group)";
717    }
718    return "??";
719  }
720
721  private void write(StringBuilder b) {
722
723    switch (kind) {
724    case Name:
725      b.append(name);
726      break;
727    case Constant:
728      b.append(constant);
729      break;
730    case Function:
731      b.append(function.toCode());
732      b.append('(');
733      boolean f = true;
734      for (ExpressionNode n : parameters) {
735        if (f)
736          f = false;
737        else
738          b.append(", ");
739        n.write(b);
740      }
741      b.append(')');
742
743      break;
744    case Group:
745      b.append('(');
746      group.write(b);
747      b.append(')');
748    }
749
750    if (inner != null) {
751      b.append('.');
752      inner.write(b);
753    }
754    if (operation != null) {
755      b.append(' ');
756      b.append(operation.toCode());
757      b.append(' ');
758      opNext.write(b);
759    }
760  }
761
762  public String check() {
763
764    switch (kind) {
765    case Name:
766      if (Utilities.noString(name))
767        return "No Name provided @ " + location();
768      break;
769
770    case Function:
771      if (function == null)
772        return "No Function id provided @ " + location();
773      for (ExpressionNode n : parameters) {
774        String msg = n.check();
775        if (msg != null)
776          return msg;
777      }
778
779      break;
780
781    case Constant:
782      if (Utilities.noString(constant))
783        return "No Constant provided @ " + location();
784      break;
785
786    case Group:
787      if (group == null)
788        return "No Group provided @ " + location();
789      else {
790        String msg = group.check();
791        if (msg != null)
792          return msg;
793      }
794    }
795    if (inner != null) {
796      String msg = inner.check();
797      if (msg != null)
798        return msg;
799    }
800    if (operation == null) {
801
802      if (opNext != null)
803        return "Next provided when it shouldn't be @ " + location();
804    } else {
805      if (opNext == null)
806        return "No Next provided @ " + location();
807      else
808        opNext.check();
809    }
810    return null;
811
812  }
813
814  private String location() {
815    return Integer.toString(start.getLine()) + ", " + Integer.toString(start.getColumn());
816  }
817
818  public TypeDetails getTypes() {
819    return types;
820  }
821
822  public void setTypes(TypeDetails types) {
823    this.types = types;
824  }
825
826  public TypeDetails getOpTypes() {
827    return opTypes;
828  }
829
830  public void setOpTypes(TypeDetails opTypes) {
831    this.opTypes = opTypes;
832  }
833
834}