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