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