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