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