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