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