001package org.hl7.fhir.r4.model;
002
003import java.util.ArrayList;
004import java.util.List;
005import org.hl7.fhir.utilities.Utilities;
006
007public class ExpressionNode {
008
009  public enum Kind {
010                Name, Function, Constant, Group
011        }
012        public static class SourceLocation {
013                private int line;
014                private int column;
015                public  SourceLocation(int line, int column) {
016                        super();
017                        this.line = line;
018                        this.column = column;
019                }
020                public int getLine() {
021                        return line;
022                }
023                public int getColumn() {
024                        return column;
025                }
026                public void setLine(int line) {
027                        this.line = line;
028                }
029                public void setColumn(int column) {
030                        this.column = column;
031                }
032
033                public String toString() {
034                        return Integer.toString(line)+", "+Integer.toString(column);
035                }
036        }
037  public enum Function {
038    Custom, 
039    
040    Empty, Not, Exists, SubsetOf, SupersetOf, IsDistinct, Distinct, Count, Where, Select, All, Repeat, Aggregate, Item /*implicit from name[]*/, As, Is, Single,
041    First, Last, Tail, Skip, Take, Union, Combine, Intersect, Exclude, Iif, Upper, Lower, ToChars, Substring, StartsWith, EndsWith, Matches, ReplaceMatches, Contains, Replace, Length,  
042    Children, Descendants, MemberOf, Trace, Today, Now, Resolve, Extension, HasValue, AliasAs, Alias, HtmlChecks, OfType, Type,
043    IsBoolean, IsInteger, IsString, IsDecimal, IsQuantity, IsDateTime, IsTime, ToBoolean, ToInteger, ToString, ToDecimal, ToQuantity, ToDateTime, ToTime;
044
045    public static Function fromCode(String name) {
046      if (name.equals("empty")) return Function.Empty;
047      if (name.equals("not")) return Function.Not;
048      if (name.equals("exists")) return Function.Exists;
049      if (name.equals("subsetOf")) return Function.SubsetOf;
050      if (name.equals("supersetOf")) return Function.SupersetOf;
051      if (name.equals("isDistinct")) return Function.IsDistinct;
052      if (name.equals("distinct")) return Function.Distinct;
053      if (name.equals("count")) return Function.Count;
054      if (name.equals("where")) return Function.Where;
055      if (name.equals("select")) return Function.Select;
056      if (name.equals("all")) return Function.All;
057      if (name.equals("repeat")) return Function.Repeat;
058      if (name.equals("aggregate")) return Function.Aggregate;      
059      if (name.equals("item")) return Function.Item;
060      if (name.equals("as")) return Function.As;
061      if (name.equals("is")) return Function.Is;
062      if (name.equals("single")) return Function.Single;
063      if (name.equals("first")) return Function.First;
064      if (name.equals("last")) return Function.Last;
065      if (name.equals("tail")) return Function.Tail;
066      if (name.equals("skip")) return Function.Skip;
067      if (name.equals("take")) return Function.Take;
068      if (name.equals("union")) return Function.Union;
069      if (name.equals("combine")) return Function.Combine;
070      if (name.equals("intersect")) return Function.Intersect;
071      if (name.equals("exclude")) return Function.Exclude;
072      if (name.equals("iif")) return Function.Iif;
073      if (name.equals("lower")) return Function.Lower;
074      if (name.equals("upper")) return Function.Upper;
075      if (name.equals("toChars")) return Function.ToChars;
076      if (name.equals("substring")) return Function.Substring;
077      if (name.equals("startsWith")) return Function.StartsWith;
078      if (name.equals("endsWith")) return Function.EndsWith;
079      if (name.equals("matches")) return Function.Matches;
080      if (name.equals("replaceMatches")) return Function.ReplaceMatches;
081      if (name.equals("contains")) return Function.Contains;
082      if (name.equals("replace")) return Function.Replace;
083      if (name.equals("length")) return Function.Length;
084      if (name.equals("children")) return Function.Children;
085      if (name.equals("descendants")) return Function.Descendants;
086      if (name.equals("memberOf")) return Function.MemberOf;
087      if (name.equals("trace")) return Function.Trace;
088      if (name.equals("today")) return Function.Today;
089      if (name.equals("now")) return Function.Now;
090      if (name.equals("resolve")) return Function.Resolve;
091      if (name.equals("extension")) return Function.Extension;
092      if (name.equals("hasValue")) return Function.HasValue;
093      if (name.equals("alias")) return Function.Alias;
094      if (name.equals("aliasAs")) return Function.AliasAs;
095      if (name.equals("htmlchecks")) return Function.HtmlChecks;
096      if (name.equals("ofType")) return Function.OfType;      
097      if (name.equals("type")) return Function.Type;      
098      if (name.equals("toInteger")) return Function.ToInteger;
099      if (name.equals("toDecimal")) return Function.ToDecimal;
100      if (name.equals("toString")) return Function.ToString;
101      if (name.equals("toQuantity")) return Function.ToQuantity;
102      if (name.equals("toBoolean")) return Function.ToBoolean;
103      if (name.equals("toDateTime")) return Function.ToDateTime;
104      if (name.equals("toTime")) return Function.ToTime;
105      if (name.equals("isInteger")) return Function.IsInteger;
106      if (name.equals("isDecimal")) return Function.IsDecimal;
107      if (name.equals("isString")) return Function.IsString;
108      if (name.equals("isQuantity")) return Function.IsQuantity;
109      if (name.equals("isBoolean")) return Function.IsBoolean;
110      if (name.equals("isDateTime")) return Function.IsDateTime;
111      if (name.equals("isTime")) return Function.IsTime;
112      return null;
113    }
114    public String toCode() {
115      switch (this) {
116      case Empty : return "empty";
117      case Not : return "not";
118      case Exists : return "exists";
119      case SubsetOf : return "subsetOf";
120      case SupersetOf : return "supersetOf";
121      case IsDistinct : return "isDistinct";
122      case Distinct : return "distinct";
123      case Count : return "count";
124      case Where : return "where";
125      case Select : return "select";
126      case All : return "all";
127      case Repeat : return "repeat";
128      case Aggregate : return "aggregate";
129      case Item : return "item";
130      case As : return "as";
131      case Is : return "is";
132      case Single : return "single";
133      case First : return "first";
134      case Last : return "last";
135      case Tail : return "tail";
136      case Skip : return "skip";
137      case Take : return "take";
138      case Union : return "union";
139      case Combine : return "combine";
140      case Intersect : return "intersect";
141      case Exclude : return "exclude";
142      case Iif : return "iif";
143      case ToChars : return "toChars";
144      case Lower : return "lower";
145      case Upper : return "upper";
146      case Substring : return "substring";
147      case StartsWith : return "startsWith";
148      case EndsWith : return "endsWith";
149      case Matches : return "matches";
150      case ReplaceMatches : return "replaceMatches";
151      case Contains : return "contains";
152      case Replace : return "replace";
153      case Length : return "length";
154      case Children : return "children";
155      case Descendants : return "descendants";
156      case MemberOf : return "memberOf";
157      case Trace : return "trace";
158      case Today : return "today";
159      case Now : return "now";
160      case Resolve : return "resolve";
161      case Extension : return "extension";
162      case HasValue : return "hasValue";
163      case Alias : return "alias";
164      case AliasAs : return "aliasAs";
165      case HtmlChecks : return "htmlchecks";
166      case OfType : return "ofType";
167      case Type : return "type";
168      case ToInteger : return "toInteger";
169      case ToDecimal : return "toDecimal";
170      case ToString : return "toString";
171      case ToBoolean : return "toBoolean";
172      case ToQuantity : return "toQuantity";
173      case ToDateTime : return "toDateTime";
174      case ToTime : return "toTime";
175      case IsInteger : return "isInteger";
176      case IsDecimal : return "isDecimal";
177      case IsString : return "isString";
178      case IsBoolean : return "isBoolean";
179      case IsQuantity : return "isQuantity";
180      case IsDateTime : return "isDateTime";
181      case IsTime : return "isTime";
182      default: return "??";
183      }
184    }
185  }
186
187        public enum Operation {
188                Equals, Equivalent, NotEquals, NotEquivalent, LessThen, Greater, LessOrEqual, GreaterOrEqual, Is, As, Union, Or, And, Xor, Implies, 
189                Times, DivideBy, Plus, Minus, Concatenate, Div, Mod, In, Contains, MemberOf;
190
191                public static Operation fromCode(String name) {
192                        if (Utilities.noString(name))
193                                return null;
194                        if (name.equals("="))
195                                return Operation.Equals;
196                        if (name.equals("~"))
197                                return Operation.Equivalent;
198                        if (name.equals("!="))
199                                return Operation.NotEquals;
200                        if (name.equals("!~"))
201                                return Operation.NotEquivalent;
202                        if (name.equals(">"))
203                                return Operation.Greater;
204                        if (name.equals("<"))
205                                return Operation.LessThen;
206                        if (name.equals(">="))
207                                return Operation.GreaterOrEqual;
208                        if (name.equals("<="))
209                                return Operation.LessOrEqual;
210                        if (name.equals("|"))
211                                return Operation.Union;
212                        if (name.equals("or"))
213                                return Operation.Or;
214                        if (name.equals("and"))
215                                return Operation.And;
216                        if (name.equals("xor"))
217                                return Operation.Xor;
218      if (name.equals("is"))
219        return Operation.Is;
220      if (name.equals("as"))
221        return Operation.As;
222      if (name.equals("*"))
223        return Operation.Times;
224      if (name.equals("/"))
225        return Operation.DivideBy;
226                        if (name.equals("+"))
227                                return Operation.Plus;
228      if (name.equals("-"))
229        return Operation.Minus;
230      if (name.equals("&"))
231        return Operation.Concatenate;
232                        if (name.equals("implies"))
233                                return Operation.Implies;
234      if (name.equals("div"))
235        return Operation.Div;
236      if (name.equals("mod"))
237        return Operation.Mod;
238      if (name.equals("in"))
239        return Operation.In;
240      if (name.equals("contains"))
241        return Operation.Contains;
242      if (name.equals("memberOf"))
243        return Operation.MemberOf;      
244                        return null;
245
246                }
247                public String toCode() {
248            switch (this) {
249                        case Equals : return "=";
250                        case Equivalent : return "~";
251                        case NotEquals : return "!=";
252                        case NotEquivalent : return "!~";
253                        case Greater : return ">";
254                        case LessThen : return "<";
255                        case GreaterOrEqual : return ">=";
256                        case LessOrEqual : return "<=";
257                        case Union : return "|";
258                        case Or : return "or";
259                        case And : return "and";
260                        case Xor : return "xor";
261      case Times : return "*";
262      case DivideBy : return "/";
263      case Plus : return "+";
264      case Minus : return "-";
265      case Concatenate : return "&";
266                        case Implies : return "implies";
267      case Is : return "is";
268      case As : return "as";
269      case Div : return "div";
270      case Mod : return "mod";
271      case In : return "in";
272      case Contains : return "contains";
273      case MemberOf : return "memberOf";
274                        default: return "??";
275                        }
276                }
277        }
278
279  public enum CollectionStatus {
280    SINGLETON, ORDERED, UNORDERED;
281  }
282  
283  //the expression will have one of either name or constant
284        private String uniqueId;
285        private Kind kind;
286        private String name;
287        private Base constant;
288        private Function function;
289        private List<ExpressionNode> parameters; // will be created if there is a function
290        private ExpressionNode inner;
291        private ExpressionNode group;
292        private Operation operation;
293        private boolean proximal; // a proximal operation is the first in the sequence of operations. This is significant when evaluating the outcomes
294        private ExpressionNode opNext;
295        private SourceLocation start;
296        private SourceLocation end;
297        private SourceLocation opStart;
298        private SourceLocation opEnd;
299        private TypeDetails types;
300        private TypeDetails opTypes;
301
302
303        public ExpressionNode(int uniqueId) {
304                super();
305                this.uniqueId = Integer.toString(uniqueId);
306        }
307
308        public String toString() {
309                StringBuilder b = new StringBuilder();
310                switch (kind) {
311                case Name:
312                        b.append(name);
313                        break;
314                case Function:
315                        if (function == Function.Item) 
316                                b.append("[");
317                        else {
318                                b.append(name);
319                                b.append("(");
320                        }
321                        boolean first = true;
322                        for (ExpressionNode n : parameters) {
323                                if (first)
324                                        first = false;
325                                else
326                                        b.append(", ");
327                                b.append(n.toString());
328                        }
329                        if (function == Function.Item) 
330                                b.append("]");
331                        else {
332                                b.append(")");
333                        }
334                        break;
335                case Constant:
336                  if (constant instanceof StringType)
337              b.append("'"+Utilities.escapeJson(constant.primitiveValue())+"'");
338                  else if (constant instanceof Quantity) {
339                    Quantity q = (Quantity) constant;
340        b.append(Utilities.escapeJson(q.getValue().toPlainString()));
341        b.append(" '");
342        b.append(Utilities.escapeJson(q.getUnit()));
343        b.append("'");
344                  } else
345                    b.append(Utilities.escapeJson(constant.primitiveValue()));
346                        break;
347                case Group:
348                        b.append("(");
349                        b.append(group.toString());
350                        b.append(")");
351                }
352                if (inner != null) {
353                        b.append(".");
354                        b.append(inner.toString());
355                }
356                if (operation != null) {
357                        b.append(" ");
358                        b.append(operation.toCode());
359                        b.append(" ");
360                        b.append(opNext.toString());
361                }
362                        
363                return b.toString();
364        }
365        
366        public String getName() {
367                return name;
368        }
369        public void setName(String name) {
370                this.name = name;
371        }
372        public Base getConstant() {
373                return constant;
374        }
375        public void setConstant(Base constant) {
376                this.constant = constant;
377        }
378        
379        public Function getFunction() {
380                return function;
381        }
382        public void setFunction(Function function) {
383                this.function = function;
384                if (parameters == null)
385                        parameters = new ArrayList<ExpressionNode>();
386        }
387
388        public boolean isProximal() {
389                return proximal;
390        }
391        public void setProximal(boolean proximal) {
392                this.proximal = proximal;
393        }
394        public Operation getOperation() {
395                return operation;
396        }
397        public void setOperation(Operation operation) {
398                this.operation = operation;
399        }
400        public ExpressionNode getInner() {
401                return inner;
402        }
403        public void setInner(ExpressionNode value) {
404                this.inner = value;
405        }
406        public ExpressionNode getOpNext() {
407                return opNext;
408        }
409        public void setOpNext(ExpressionNode value) {
410                this.opNext = value;
411        }
412        public List<ExpressionNode> getParameters() {
413                return parameters;
414        }
415        public boolean checkName() {
416                if (!name.startsWith("$"))
417                        return true;
418                else
419                        return Utilities.existsInList(name, "$this", "$total");  
420        }
421
422        public Kind getKind() {
423                return kind;
424        }
425
426        public void setKind(Kind kind) {
427                this.kind = kind;
428        }
429
430        public ExpressionNode getGroup() {
431                return group;
432        }
433
434        public void setGroup(ExpressionNode group) {
435                this.group = group;
436        }
437
438        public SourceLocation getStart() {
439                return start;
440        }
441
442        public void setStart(SourceLocation start) {
443                this.start = start;
444        }
445
446        public SourceLocation getEnd() {
447                return end;
448        }
449
450        public void setEnd(SourceLocation end) {
451                this.end = end;
452        }
453
454        public SourceLocation getOpStart() {
455                return opStart;
456        }
457
458        public void setOpStart(SourceLocation opStart) {
459                this.opStart = opStart;
460        }
461
462        public SourceLocation getOpEnd() {
463                return opEnd;
464        }
465
466        public void setOpEnd(SourceLocation opEnd) {
467                this.opEnd = opEnd;
468        }
469
470        public String getUniqueId() {
471                return uniqueId;
472        }
473
474
475        public int parameterCount() {
476                if (parameters == null)
477                        return 0;
478                else
479                        return parameters.size();
480        }
481
482        public String Canonical() {
483                StringBuilder b = new StringBuilder();
484                write(b);
485                return b.toString();
486        }
487
488        public String summary() {
489                switch (kind) {
490                case Name: return uniqueId+": "+name;
491                case Function: return uniqueId+": "+function.toString()+"()";
492                case Constant: return uniqueId+": "+constant;
493                case Group: return uniqueId+": (Group)";
494                }
495                return "??";
496        }
497
498        private void write(StringBuilder b) {
499
500                switch (kind) {
501                case Name:
502                        b.append(name);
503                        break;
504                case Constant:
505                        b.append(constant);
506                        break;
507                case Function:
508                        b.append(function.toCode());
509                        b.append('(');
510                        boolean f = true;
511                        for (ExpressionNode n : parameters) {
512                                if (f)
513                                        f = false;
514                                else
515                                        b.append(", ");
516                                n.write(b);
517                        }
518                        b.append(')');
519
520                        break;
521                case Group:
522                        b.append('(');
523                        group.write(b);
524                        b.append(')');
525                }
526
527                if (inner != null) {
528                        b.append('.');
529                        inner.write(b);
530                }
531                if (operation != null) {
532                        b.append(' ');
533                        b.append(operation.toCode());
534                        b.append(' ');
535                        opNext.write(b);
536                }
537        }
538
539        public String check() {
540
541                switch (kind) {
542                case Name:
543                        if (Utilities.noString(name)) 
544                                return "No Name provided @ "+location();
545                        break;
546
547                case Function:          
548                        if (function == null)
549                                return "No Function id provided @ "+location();
550                        for (ExpressionNode n : parameters) { 
551                                String msg = n.check();
552                                if (msg != null)
553                                        return msg;
554                        }
555
556                        break;
557
558                case Constant:
559                        if (constant == null) 
560                                return "No Constant provided @ "+location();
561                        break;
562
563                case Group:
564                        if (group == null)
565                                return "No Group provided @ "+location();
566                        else {
567                                String msg = group.check();
568                                if (msg != null)
569                                        return msg;
570                        }
571                }
572                if (inner != null) { 
573                        String msg = inner.check();
574                        if (msg != null)
575                                return msg;
576                }
577                if (operation == null) {
578
579                        if (opNext != null)
580                                return "Next provided when it shouldn't be @ "+location();
581                } 
582                else {
583                        if (opNext == null)
584                                return "No Next provided @ "+location();
585                        else
586                                opNext.check();
587                }
588                return null;
589
590        }
591
592        private String location() {
593                return Integer.toString(start.line)+", "+Integer.toString(start.column);
594        }
595
596        public TypeDetails getTypes() {
597                return types;
598        }
599
600        public void setTypes(TypeDetails types) {
601                this.types = types;
602        }
603
604        public TypeDetails getOpTypes() {
605                return opTypes;
606        }
607
608        public void setOpTypes(TypeDetails opTypes) {
609                this.opTypes = opTypes;
610        }
611                
612}