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