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