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