001package daikon.tools.jtb;
002
003import daikon.*;
004import daikon.inv.Equality;
005import daikon.inv.Invariant;
006import daikon.inv.filter.*;
007import java.io.PrintWriter;
008import java.io.Reader;
009import java.io.StringReader;
010import java.io.StringWriter;
011import java.io.Writer;
012import java.lang.reflect.Constructor;
013import java.lang.reflect.Method;
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.Enumeration;
017import java.util.HashSet;
018import java.util.Iterator;
019import java.util.List;
020import java.util.Set;
021import java.util.Vector;
022import jtb.JavaParser;
023import jtb.ParseException;
024import jtb.syntaxtree.*;
025import jtb.visitor.*;
026import org.checkerframework.checker.nullness.qual.Nullable;
027import org.checkerframework.checker.signature.qual.BinaryName;
028import org.checkerframework.checker.signature.qual.ClassGetName;
029import org.checkerframework.checker.signature.qual.Identifier;
030import org.plumelib.reflection.Signatures;
031import org.plumelib.util.StringsPlume;
032
033/** Static methods for manipulating the AST. */
034@SuppressWarnings({"rawtypes", "nullness"}) // not generics-correct
035public class Ast {
036
037  /** Do not instantiate. */
038  private Ast() {
039    throw new Error("Do not instantiate");
040  }
041
042  /** The line separator. */
043  private static final String lineSep = System.lineSeparator();
044
045  // ///////////////////////////////////////////////////////////////////////////
046  // Visitors
047  //
048
049  // Reads an AST from the input stream, applies the visitor to the AST,
050  // reformats only to insert comments, and writes the resulting AST to the
051  // output stream.
052  public static void applyVisitorInsertComments(
053      String javafilename, Node root, Writer output, AnnotateVisitor visitor) {
054    root.accept(visitor);
055    root.accept(new InsertCommentFormatter(visitor.addedComments));
056    try (PrintWriter writer = new PrintWriter(output, true)) {
057      for (int i = 0; i < visitor.javaFileLines.size(); i++) {
058        writer.println(visitor.javaFileLines.get(i));
059      }
060    }
061
062    // root.accept(new TreeDumper(output));
063  }
064
065  // Reads an AST from the input stream, applies the visitor to the AST,
066  // completely reformats the Ast (losing previous formatting), and writes
067  // the resulting AST to the output stream.
068  public static void applyVisitorReformat(Reader input, Writer output, Visitor visitor) {
069    JavaParser parser = new JavaParser(input);
070    Node root;
071    try {
072      root = parser.CompilationUnit();
073    } catch (ParseException e) {
074      e.printStackTrace();
075      throw new Daikon.UserError("ParseException in applyVisitorReformat");
076    }
077    root.accept(visitor);
078    // This is unfortunately necessary because TreeDumper dies if line or
079    // column numbers are out of sync.  Also see InsertCommentFormatter and
080    // applyVisitorInsertComments.
081    root.accept(new TreeFormatter(2, 80));
082    root.accept(new TreeDumper(output));
083  }
084
085  // ///////////////////////////////////////////////////////////////////////////
086  // Printing and parsing
087  //
088
089  // Formats an AST as a String.
090  // This version does not reformat the tree (which blows away formatting
091  // information).  The call to "removeWhitespace" may do the wrong thing
092  // for embedded strings, however.  In any event, the result is not
093  // intended for direct human consumption.
094  public static String format(Node n) {
095    StringWriter w = new StringWriter();
096    n.accept(new TreeDumper(w));
097    // This is incorrect. A "//" comment ending in a period, for example, will
098    // cause the line after it to become part of the comment as well.
099    // If not intended for human consumption, why remove whitespace?
100    // return removeWhitespace(w.toString());
101    return removeWhitespace(quickFixForInternalComment(w.toString()));
102  }
103
104  // Formats an AST as a String.
105  // This is a debugging version that outputs all tree nodes:
106  // terminals and non-terminals alike.
107  public static String formatEntireTree(Node n) {
108    StringWriter w = new StringWriter();
109    n.accept(new TreeDumper(w, true));
110    // delete empty lines, but otherwise leave text alone
111    return w.toString().replaceAll("(?m)^[ \t]*\r?\n", "");
112  }
113
114  /**
115   * This method translates a line like.
116   *
117   * <pre>{@code
118   * a statement; // a comment
119   * }</pre>
120   *
121   * into
122   *
123   * <pre>
124   * a statement; // a comment//
125   * </pre>
126   *
127   * @param s a Java code line that might contain a comment
128   * @return the line with its comment, if any, tweaked
129   */
130  public static String quickFixForInternalComment(String s) {
131    StringBuilder b = new StringBuilder();
132    String[] split = StringsPlume.splitLines(s);
133    for (int i = 0; i < split.length; i++) {
134      String line = split[i];
135      b.append(line);
136      if (line.indexOf("//") != -1) {
137        b.append("//");
138        b.append(lineSep);
139        b.append("/* */");
140      }
141      b.append(lineSep);
142    }
143    return b.toString();
144  }
145
146  // Formats the line enclosing a node
147  public static String formatCurrentLine(Node n) {
148    Node current = n;
149    while (current.getParent() != null && format(current.getParent()).indexOf(lineSep) < 0) {
150      current = current.getParent();
151    }
152    return format(current);
153  }
154
155  /**
156   * Creates an AST from a String.
157   *
158   * @param type the type of the result
159   * @param stringRep the string to parse
160   * @return an AST created from the string
161   */
162  public static Node create(String type, String stringRep) {
163    return create(type, new Class<?>[] {}, new Object[] {}, stringRep);
164  }
165
166  // Creates an AST from a String
167  public static Node create(String type, Class<?>[] argTypes, Object[] args, String stringRep) {
168    JavaParser parser = new JavaParser(new StringReader(stringRep));
169    Node n;
170    try {
171      Method m = JavaParser.class.getMethod(type, argTypes);
172      n = (Node) m.invoke(parser, args);
173    } catch (Exception e) {
174      System.err.println("create(" + type + ", \"" + stringRep + "\")");
175      e.printStackTrace();
176      throw new Daikon.UserError("Error in Ast.create");
177    }
178    return n;
179  }
180
181  // ///////////////////////////////////////////////////////////////////////////
182  // Names (fully qualified and otherwise)
183  //
184
185  /**
186   * Returns true if the string is an access modifier: "public, "protected", or "private".
187   *
188   * @param s a string
189   * @return true if the string is an access modifier
190   */
191  public static boolean isAccessModifier(String s) {
192    return s.equals("public") || s.equals("protected") || s.equals("private");
193  }
194
195  /**
196   * Returns the name of the formal parameter.
197   *
198   * @param p a formal parameter
199   * @return the name of the formal parameter
200   */
201  // f4 -> VariableDeclaratorId()
202  @SuppressWarnings("signature:return") // string manipulation
203  public static @Identifier String getName(FormalParameter p) {
204    String name = format(p.f4);
205    int startBrackets = name.indexOf('[');
206    if (startBrackets == -1) {
207      return name;
208    } else {
209      return name.substring(0, startBrackets);
210    }
211  }
212
213  // f2 -> Type()
214  // f4 -> VariableDeclaratorId()
215  public static String getType(FormalParameter fp) {
216
217    StringWriter w = new StringWriter();
218    fp.accept(new TreeDumper(w));
219
220    FormalParameter p = (FormalParameter) create("FormalParameter", w.toString());
221
222    p.accept(new TreeFormatter());
223
224    String type = format(p.f2);
225    String name = format(p.f4);
226
227    // format() removes whitespace around brackets, so this test is safe.
228    while (name.endsWith("[]")) {
229      type += "[]";
230      name = name.substring(0, name.length() - 2);
231    }
232
233    return type;
234  }
235
236  /**
237   * Returns the name of the method.
238   *
239   * @param m a method
240   * @return the name of the method
241   */
242  // f2 -> MethodDeclarator()
243  @SuppressWarnings("signature:return") // JTB is not annotated
244  public static @Identifier String getName(MethodDeclaration m) {
245    return m.f2.f0.tokenImage;
246  }
247
248  /**
249   * Returns the name of the constructor.
250   *
251   * @param m a constructor
252   * @return the name of the constructor
253   */
254  // f1 -> <IDENTIFIER>
255  @SuppressWarnings("signature:return") // JTB is not annotated
256  public static @Identifier String getName(ConstructorDeclaration m) {
257    return m.f1.tokenImage;
258  }
259
260  // Returns the name of the package for this compilation unit, or null if
261  // no package was specified.
262  public static @Nullable String getPackage(CompilationUnit u) {
263    NodeOptional o = u.f0;
264    if (o.present()) {
265      PackageDeclaration p = (PackageDeclaration) o.node;
266      return format(p.f2); // f2 -> Name()
267    } else {
268      return null;
269    }
270  }
271
272  // Return the fully qualified name of the method (not including params).
273  // <package>.<class>*.<method>
274  // If the method is in an anonymous inner class, "$inner" is used to
275  // represent the name of the inner class.
276  public static String getFullName(MethodDeclaration method) {
277    String className = getClassName(method);
278    String methodName = getName(method);
279    return className + "." + methodName;
280  }
281
282  // Used to be called "getFullName", but that was misleading.
283  // Returns the fully qualified signature of a method.
284  // <package>.<class>*.<method>(<params>)
285  // If the method is in an anonymous inner class, "$inner" is used to
286  // represent the name of the inner class.
287  public static String getFullSignature(MethodDeclaration method) {
288    String className = getClassName(method);
289    String methodDeclarator = getMethodDeclarator(method);
290    return className + "." + methodDeclarator;
291  }
292
293  /**
294   * Returns the classname if the given type declaration declares a ClassOrInterfaceDeclaration.
295   * Otherwise returns null.
296   */
297  public static @Nullable @BinaryName String getClassNameForType(TypeDeclaration d) {
298
299    // Grammar production for TypeDeclaration:
300    // f0 -> ";"
301    //       | Modifiers() ( ClassOrInterfaceDeclaration(modifiers) | EnumDeclaration(modifiers) |
302    // AnnotationTypeDeclaration(modifiers) )
303
304    NodeChoice c = d.f0;
305    if (c.which == 0) {
306      return null;
307    } else {
308      NodeSequence seq = (NodeSequence) c.choice;
309      NodeChoice c2 = (NodeChoice) seq.elementAt(1);
310      if (c2.choice instanceof ClassOrInterfaceDeclaration) {
311        return getClassName(c2.choice);
312      } else {
313        return null;
314      }
315    }
316  }
317
318  /** Returns the fully qualified name of the class containing the node. */
319  public static @BinaryName String getClassName(Node d) {
320
321    ClassOrInterfaceDeclaration n =
322        (d instanceof ClassOrInterfaceDeclaration)
323            ? (ClassOrInterfaceDeclaration) d
324            : (ClassOrInterfaceDeclaration) Ast.getParent(ClassOrInterfaceDeclaration.class, d);
325
326    String packageName;
327    CompilationUnit unit = (CompilationUnit) getParent(CompilationUnit.class, n);
328    String getPackage = getPackage(unit);
329    if (getPackage != null) {
330      packageName = getPackage + ".";
331    } else {
332      packageName = "";
333    }
334
335    String className = n.f1.tokenImage + "."; // f1 -> <IDENTIFIER>
336
337    Node currentNode = n;
338    while (true) {
339      ClassOrInterfaceBody b =
340          (ClassOrInterfaceBody) getParent(ClassOrInterfaceBody.class, currentNode);
341      if (b == null) {
342        break;
343      }
344      Node n1 = b.getParent();
345      assert n1 instanceof ClassOrInterfaceDeclaration;
346      if (isInner((ClassOrInterfaceDeclaration) n1)) {
347        // TODO: This works for anonymous classes (maybe), but is wrong for
348        // non-anonymous inner classes.
349        className = "$inner." + className;
350        currentNode = b;
351      } else {
352        String s = ((ClassOrInterfaceDeclaration) n1).f1.tokenImage;
353        className = s + "." + className;
354        currentNode = n1;
355      }
356    }
357
358    String result = packageName + className;
359    if (result.endsWith(".")) {
360      result = result.substring(0, result.length() - 1);
361    }
362
363    @SuppressWarnings("signature") // string concatenation, etc.
364    @BinaryName String result_bnfna = result;
365    return result_bnfna;
366  }
367
368  // f2 -> MethodDeclarator()
369  public static void setName(MethodDeclaration m, String name) {
370    m.f2.f0.tokenImage = name;
371  }
372
373  // f1 -> [ AssignmentOperator() Expression() ]
374  // Return the primary expression on the left-hand side of an assignment
375  public static PrimaryExpression assignment2primaryexpression(Expression n) {
376    // All this could perhaps be replaced with an ad-hoc visitor, as is
377    // done in nodeTokenAfter().  But it is written now, so leave it as is.
378
379    assert n.f1.present();
380    ConditionalExpression ce = n.f0;
381    assert !ce.f1.present();
382    ConditionalOrExpression coe = ce.f0;
383    assert !coe.f1.present();
384    ConditionalAndExpression cae = coe.f0;
385    assert !cae.f1.present();
386    InclusiveOrExpression ioe = cae.f0;
387    assert !ioe.f1.present();
388    ExclusiveOrExpression eoe = ioe.f0;
389    assert !eoe.f1.present();
390    AndExpression ande = eoe.f0;
391    assert !ande.f1.present();
392    EqualityExpression ee = ande.f0;
393    assert !ee.f1.present();
394    InstanceOfExpression iofe = ee.f0;
395    assert !iofe.f1.present();
396    RelationalExpression re = iofe.f0;
397    assert !re.f1.present();
398    ShiftExpression se = re.f0;
399    assert !se.f1.present();
400    AdditiveExpression adde = se.f0;
401    assert !adde.f1.present();
402    MultiplicativeExpression me = adde.f0;
403    assert !me.f1.present();
404    UnaryExpression ue = me.f0;
405    UnaryExpressionNotPlusMinus uenpm = (UnaryExpressionNotPlusMinus) ue.f0.choice;
406    PostfixExpression pfe = (PostfixExpression) uenpm.f0.choice;
407    assert !pfe.f1.present();
408    PrimaryExpression pe = pfe.f0;
409    return pe;
410  }
411
412  public static String fieldName(PrimaryExpression pe) {
413
414    // System.out.println("fieldName(" + pe + ")");
415
416    // PrimaryExpression:
417    // f0 -> PrimaryPrefix()
418    // f1 -> ( PrimarySuffix() )*
419
420    // First, try to get a name from the PrimarySuffix.
421
422    NodeListOptional pslist = pe.f1;
423    if (pslist.size() > 0) {
424      PrimarySuffix ps = (PrimarySuffix) pslist.elementAt(pslist.size() - 1);
425      NodeChoice psnc = ps.f0;
426      // PrimarySuffix:
427      // f0 -> "." "super"
428      //       | "." "this"
429      //       | "." AllocationExpression()
430      //       | MemberSelector()
431      //       | "[" Expression() "]"
432      //       | "." <IDENTIFIER>
433      //       | Arguments()
434      switch (psnc.which) {
435        case 5:
436          NodeSequence sn = (NodeSequence) psnc.choice;
437          assert sn.size() == 2;
438          return ((NodeToken) sn.elementAt(1)).tokenImage;
439      }
440    }
441
442    // If it was impossible to get a name from the PrimarySuffix,
443    // try the PrimaryPrefix.
444
445    // PrimaryPrefix:
446    // f0 -> Literal()
447    //       | ( <IDENTIFIER> "." )* "this"
448    //       | "super" "." <IDENTIFIER>
449    //       | ClassOrInterfaceType() "." "super" "." <IDENTIFIER>
450    //       | "(" Expression() ")"
451    //       | AllocationExpression()
452    //       | ResultType() "." "class"
453    //       | Name()
454    PrimaryPrefix pp = pe.f0;
455    NodeChoice ppnc = pp.f0;
456    switch (ppnc.which) {
457      case 2:
458        NodeSequence sn = (NodeSequence) ppnc.choice;
459        assert sn.size() == 3;
460        return ((NodeToken) sn.elementAt(2)).tokenImage;
461      case 7:
462        return fieldName((Name) ppnc.choice);
463    }
464
465    return null;
466  }
467
468  public static String fieldName(Name n) {
469    NodeListOptional nlo = n.f1;
470    if (nlo.present()) {
471      NodeSequence ns = (NodeSequence) nlo.elementAt(nlo.size() - 1);
472      assert ns.size() == 2;
473      NodeToken nt = (NodeToken) ns.elementAt(1);
474      return nt.tokenImage;
475    } else {
476      return n.f0.tokenImage;
477    }
478  }
479
480  // ///////////////////////////////////////////////////////////////////////////
481  // Comments
482  //
483
484  // Add the comment to the first regular token in the tree, after all
485  // other special tokens (comments) associated with that token.
486  public static NodeToken addComment(Node n, String comment) {
487    return addComment(n, comment, false);
488  }
489
490  // Add the comment to the first regular token in the tree.
491  // If first is true, then it is inserted before all other special tokens;
492  // otherwise, it is inserted after them.
493  public static NodeToken addComment(Node n, String comment, boolean first) {
494    NodeToken nt = new NodeToken(comment);
495    addComment(n, nt, first);
496    return nt;
497  }
498
499  // Add the comment to the first regular token in the tree, after all
500  // other special tokens (comments) associated with that token.
501  public static void addComment(Node n, NodeToken comment) {
502    addComment(n, comment, false);
503  }
504
505  /**
506   * Add the comment to the first regular token in the tree, as a "special token" (comment).
507   *
508   * <p>If first is true, then it is inserted before all other special tokens; otherwise, it is
509   * inserted after them.
510   *
511   * <p>Postcondition (make sure you preserve it if you modify this method): comment.beginLine and
512   * comment.beginColumn have been assigned the line and column number where this comment will go.
513   */
514  public static void addComment(Node n, NodeToken comment, boolean first) {
515    class AddCommentVisitor extends DepthFirstVisitor {
516      private boolean seenToken = false;
517      private final NodeToken comment;
518      private final boolean first;
519
520      AddCommentVisitor(NodeToken comment, boolean first) {
521        this.comment = comment;
522        this.first = first;
523      }
524
525      @Override
526      public void visit(NodeToken node) {
527        if (!seenToken) {
528          seenToken = true;
529          if (first && (node.numSpecials() > 0)) {
530            comment.beginLine = node.getSpecialAt(0).beginLine;
531            comment.beginColumn = node.getSpecialAt(0).beginColumn;
532            // System.out.println("Set from special <<<" + node.getSpecialAt(0).trim() + ">>>");
533          } else {
534            comment.beginLine = node.beginLine;
535            comment.beginColumn = node.beginColumn;
536          }
537          if (first) {
538            addFirstSpecial(node, comment);
539          } else {
540            node.addSpecial(comment);
541          }
542          // System.out.println("comment (" + comment.beginLine + "," + comment.beginColumn + ") = "
543          // + comment.tokenImage + "; node (" + node.beginLine + "," + node.beginColumn + ")= " +
544          // node.tokenImage);
545        }
546      }
547    }
548
549    // String briefComment = comment.toString();
550    // if (briefComment.length() > 70)
551    //     briefComment = briefComment.substring(0,70) + "...";
552    // System.out.printf("addComment at %d:%d, first=%b%n  <<<%s>>>%n",
553    //                    comment.beginLine, comment.beginColumn, first, briefComment);
554
555    n.accept(new AddCommentVisitor(comment, first));
556  }
557
558  // This seems to set the beginLine and beginColumn for the comment, but
559  // how is the comment inserted into the tree?  The lines that ought to do
560  // that are commented out (without explanation).  The explanation is that
561  // despite the comment, this does NOT do insertion.  It just determines
562  // where the insertion ought to occur.  The change forces the client to
563  // do the insertion.  This should be documented/fixed.  -MDE 7/13/2003
564
565  /**
566   * Add the comment to the first regular token in the tree. If first is true, then it is inserted
567   * before all other special tokens; otherwise, it is inserted after them.
568   *
569   * <p>Exception: If first is true, may heuristically place the comment it in the middle of the
570   * list of special tokens (comments), in order to place it at the same column as the real node.
571   *
572   * <p>Postcondition (make sure you preserve it if you modify this method): comment.beginLine and
573   * comment.beginColumn have been assigned the line and column number where this comment will go.
574   */
575  public static void findLineAndCol(Node n, NodeToken comment, boolean first) {
576    class AddCommentVisitor extends DepthFirstVisitor {
577      private boolean seenToken = false;
578      private final NodeToken comment;
579      private final boolean first;
580
581      AddCommentVisitor(NodeToken comment, boolean first) {
582        this.comment = comment;
583        this.first = first;
584      }
585
586      @Override
587      public void visit(NodeToken node) {
588        if (!seenToken) {
589          seenToken = true;
590          if (first
591              && (node.numSpecials() > 1)
592              && (node.getSpecialAt(0).beginColumn
593                  != node.getSpecialAt(node.numSpecials() - 1).beginColumn)) {
594            // There are multiple comments, and the first and last ones
595            // start at different columns.  Search from the end, finding
596            // the first one with a different column.
597            int i = node.numSpecials() - 1;
598            while (node.getSpecialAt(i - 1).beginColumn == node.getSpecialAt(i).beginColumn) {
599              i--;
600            }
601            comment.beginLine = node.getSpecialAt(i).beginLine;
602            comment.beginColumn = node.getSpecialAt(i).beginColumn;
603            // addNthSpecial(node, comment, i);
604          } else if (first && (node.numSpecials() > 0)) {
605            comment.beginLine = node.getSpecialAt(0).beginLine;
606            comment.beginColumn = node.getSpecialAt(0).beginColumn;
607            // System.out.println("findLineAndCol: set from special <<<" + node.getSpecialAt(0) +
608            // ">>>");
609            // addFirstSpecial(node, comment);
610          } else {
611            comment.beginLine = node.beginLine;
612            comment.beginColumn = node.beginColumn;
613            // node.addSpecial(comment);
614          }
615          // System.out.printf("comment placed at (%d,%d) = <<<%s>>> for node (%d,%d)= <<<%s>>>%n",
616          //                   comment.beginLine, comment.beginColumn, comment.tokenImage.trim(),
617          //                   node.beginLine, node.beginColumn, node.tokenImage.trim());
618        }
619      }
620    }
621    n.accept(new AddCommentVisitor(comment, first));
622  }
623
624  /** Adds the comment to the first regular token in the tree, before the ith special token. */
625  @SuppressWarnings("JdkObsolete") // JTB uses Vector
626  public static void addNthSpecial(NodeToken n, NodeToken s, int i) {
627    if (n.specialTokens == null) {
628      n.specialTokens = new Vector<NodeToken>();
629    }
630    n.specialTokens.insertElementAt(s, i);
631    s.setParent(n);
632  }
633
634  /** Adds the comment to the first regular token in the tree, *before* all other special tokens. */
635  public static void addFirstSpecial(NodeToken n, NodeToken s) {
636    addNthSpecial(n, s, 0);
637  }
638
639  // Return the first NodeToken after (all of) the specified Node.
640  public static NodeToken nodeTokenAfter(Node n) {
641    // After the traversal, the "lastNodeToken" slot contains the
642    // last NodeToken visited.
643    class LastNodeTokenVisitor extends DepthFirstVisitor {
644      NodeToken lastNodeToken = null;
645
646      @Override
647      public void visit(NodeToken node) {
648        lastNodeToken = node;
649      }
650    }
651    // After the traversal, the "nextNodeToken" slot contains the token
652    // visited immediately after "predecessor".  ("predecessor" should be a
653    // descendant of the token from which traversal starts.)
654    class NextNodeTokenVisitor extends DepthFirstVisitor {
655      private boolean seenPredecessor = false;
656      NodeToken nextNodeToken;
657      private final NodeToken predecessor;
658
659      NextNodeTokenVisitor(NodeToken predecessor) {
660        this.predecessor = predecessor;
661      }
662
663      @Override
664      @SuppressWarnings("interning")
665      public void visit(NodeToken node) {
666        if (!seenPredecessor) {
667          if (node == predecessor) {
668            seenPredecessor = true;
669          }
670        } else if (nextNodeToken == null) {
671          nextNodeToken = node;
672        }
673      }
674    }
675
676    // set predecessor
677    LastNodeTokenVisitor lntv = new LastNodeTokenVisitor();
678    n.accept(lntv);
679    NodeToken predecessor = lntv.lastNodeToken;
680    if (predecessor == null) {
681      throw new Error("No lastNodeToken for " + n);
682    }
683
684    // We don't know how high in the tree we need to go in order to find a
685    // successor, so iteratively go higher until success.  This has bad
686    // worst-case performance, but should be acceptable in practice.
687    NodeToken result = null;
688    Node parent = n.getParent();
689    while ((result == null) && (parent != null)) {
690      NextNodeTokenVisitor nntv = new NextNodeTokenVisitor(predecessor);
691      parent.accept(nntv);
692      result = nntv.nextNodeToken;
693      parent = parent.getParent();
694    }
695    if (result == null) {
696      throw new Error("No nextNodeToken for " + n);
697    }
698    return result;
699  }
700
701  // Removes all the special tokens (annotations and other comments)
702  // from the first regular token in the method
703  public static void removeAnnotations(MethodDeclaration m) {
704    class RemoveAnnotationsVisitor extends DepthFirstVisitor {
705      private boolean seenToken = false;
706
707      @Override
708      public void visit(NodeToken n) {
709        if (!seenToken) {
710          seenToken = true;
711          n.specialTokens = null;
712        }
713      }
714    }
715
716    m.accept(new RemoveAnnotationsVisitor());
717  }
718
719  // ///////////////////////////////////////////////////////////////////////////
720  // Whitespace
721  //
722
723  /**
724   * Removes whitespace around punctuation: ., (, ), [, ].
725   *
726   * @param arg a string
727   * @return the string with whitespace removed
728   */
729  public static String removeWhitespace(String arg) {
730    arg = arg.trim();
731    arg = StringsPlume.removeWhitespaceAround(arg, ".");
732    arg = StringsPlume.removeWhitespaceAround(arg, "(");
733    arg = StringsPlume.removeWhitespaceAround(arg, ")");
734    arg = StringsPlume.removeWhitespaceAround(arg, "[");
735    arg = StringsPlume.removeWhitespaceBefore(arg, "]");
736    return arg;
737  }
738
739  // ///////////////////////////////////////////////////////////////////////////
740  // PptMap manipulation
741  //
742
743  // ///////////////////////////////////////////////////////////////////////////
744  // Reflection
745  //
746
747  /**
748   * Returns the class corresponding to the given node.
749   *
750   * @param n a node
751   * @return the class corresponding to the given node
752   */
753  public static Class<?> getClass(Node n) {
754    String ast_classname = getClassName(n);
755    if (ast_classname.indexOf("$inner") != -1) {
756      return null;
757    }
758    return getClass(ast_classname);
759  }
760
761  public static Class<?> getClass(@ClassGetName String s) {
762    try {
763      Class<?> c = Class.forName(s);
764      assert c != null;
765      return c;
766    } catch (ClassNotFoundException e) {
767      String orig_s = s;
768      // System.out.println("Lookup failed: " + s);
769      // We should have found it.  Maybe there is a name mangling problem.
770      // Systematically change each "." to "$" in an attempt to fix it.
771      while (true) {
772        int dot_pos = s.lastIndexOf('.');
773        if (dot_pos == -1) {
774          throw new Error("Didn't find class " + orig_s);
775        }
776        @SuppressWarnings("signature") // string concatenation
777        @ClassGetName String new_s = s.substring(0, dot_pos) + "$" + s.substring(dot_pos + 1);
778        s = new_s;
779        // System.out.println("Lookup trying: " + s);
780        try {
781          Class<?> c = Class.forName(s);
782          assert c != null;
783          return c;
784        } catch (ClassNotFoundException ex) {
785          throw new Error("This can't happen");
786        }
787      }
788      // throw new Error("Control cannot reach here");
789    }
790  }
791
792  public static Method getMethod(MethodDeclaration methoddecl) {
793    Class<?> c = getClass(methoddecl);
794    return getMethod(c, methoddecl);
795  }
796
797  public static Method getMethod(Class<?> c, MethodDeclaration methoddecl) {
798    String ast_methodname = getName(methoddecl);
799    List<FormalParameter> ast_params = getParameters(methoddecl);
800
801    List<Method> publicMethods = Arrays.<Method>asList(c.getMethods());
802    List<Method> declaredMethods = Arrays.<Method>asList(c.getDeclaredMethods());
803    List<Method> allMethods = new ArrayList<>();
804    allMethods.addAll(publicMethods);
805    allMethods.addAll(declaredMethods);
806
807    Method[] meths = allMethods.toArray(new Method[0]);
808
809    for (int i = 0; i < meths.length; i++) {
810
811      Method meth = meths[i];
812      // System.out.println("getMethod(" + c.getName() + ", " + getName(methoddecl) + ") checking "
813      // + meth.getName());
814      if (!typeMatch(meth.getName(), ast_methodname)) {
815        continue;
816      }
817
818      Class<?>[] params = meth.getParameterTypes();
819      if (paramsMatch(params, ast_params)) {
820        // System.out.println("getMatch succeeded: " + ppt.name());
821        return meth;
822      }
823    }
824    return null;
825  }
826
827  public static Constructor<?> getConstructor(ConstructorDeclaration constructordecl) {
828    Class<?> c = getClass(constructordecl);
829    return getConstructor(c, constructordecl);
830  }
831
832  public static Constructor<?> getConstructor(Class<?> c, ConstructorDeclaration constructordecl) {
833    String ast_constructorname = getName(constructordecl);
834
835    List<FormalParameter> ast_params = getParameters(constructordecl);
836
837    List<Constructor<?>> publicConstructors = Arrays.<Constructor<?>>asList(c.getConstructors());
838    List<Constructor<?>> declaredConstructors =
839        Arrays.<Constructor<?>>asList(c.getDeclaredConstructors());
840    List<Constructor<?>> allConstructors = new ArrayList<>();
841    allConstructors.addAll(publicConstructors);
842    allConstructors.addAll(declaredConstructors);
843
844    Constructor<?>[] constrs = allConstructors.toArray(new Constructor<?>[0]);
845
846    for (int i = 0; i < constrs.length; i++) {
847
848      Constructor<?> constr = constrs[i];
849      if (!typeMatch(constr.getName(), ast_constructorname)) {
850        continue;
851      }
852      Class<?>[] params = constr.getParameterTypes();
853      if (paramsMatch(params, ast_params)) {
854        // System.out.println("getMatch succeeded: " + ppt.name());
855        return constr;
856      }
857    }
858    return null;
859  }
860
861  public static boolean paramsMatch(Class<?>[] params, List<FormalParameter> ast_params) {
862
863    if (params.length != ast_params.size()) {
864      return false;
865    }
866    // Now check whether args match.
867    int j = 0;
868    for (Iterator<FormalParameter> itor = ast_params.iterator(); itor.hasNext(); j++) {
869      String ast_param = getType(itor.next());
870      Class<?> param = params[j];
871      // System.out.println("Comparing " + param + " to " + ast_param + ":");
872      if (!typeMatch(classnameForSourceOutput(param), ast_param)) {
873        return false;
874      }
875    }
876    return true;
877  }
878
879  // return true if m is defined in any superclass of its class
880  public static boolean isOverride(MethodDeclaration methdecl) {
881    ClassOrInterfaceDeclaration classOrInterface =
882        (ClassOrInterfaceDeclaration) getParent(ClassOrInterfaceDeclaration.class, methdecl);
883    if (classOrInterface != null && isInterface(classOrInterface)) {
884      return false;
885    }
886    Class<?> c = getClass(methdecl);
887    if (c == null) {
888      return false;
889    }
890    // System.out.println("isOverride(" + getName(methdecl) + "): class=" + c.getName() + "; super="
891    // + c.getSuperclass());
892    return isOverride(c.getSuperclass(), methdecl);
893  }
894
895  // return true if methdecl is defined in c or any of its superclasses
896  public static boolean isOverride(Class<?> c, MethodDeclaration methdecl) {
897    // System.out.println("isOverride(" + c.getName() + ", " + getName(methdecl) + ")");
898    Method meth = getMethod(c, methdecl);
899    if (meth != null) {
900      // System.out.println("isOverride => true");
901      return true;
902    }
903    Class<?> superclass = c.getSuperclass();
904    if (superclass == null) {
905      return false;
906    }
907    return isOverride(superclass, methdecl);
908  }
909
910  // return true if methdecl is defined in any interface of its class
911  public static boolean isImplementation(MethodDeclaration methdecl) {
912
913    ClassOrInterfaceDeclaration classOrInterface =
914        (ClassOrInterfaceDeclaration) getParent(ClassOrInterfaceDeclaration.class, methdecl);
915    if (classOrInterface != null && isInterface(classOrInterface)) {
916      return false;
917    }
918    Class<?> c = getClass(methdecl);
919    if (c == null) {
920      return false;
921    }
922    // System.out.println("isImplementation(" + getName(methdecl) + "): class=" + c.getName());
923    Class<?>[] interfaces = c.getInterfaces();
924    for (int i = 0; i < interfaces.length; i++) {
925      if (isImplementation(interfaces[i], methdecl)) {
926        return true;
927      }
928    }
929    return false;
930  }
931
932  // return true if methdecl is defined in c or any of its interfaces
933  public static boolean isImplementation(Class<?> c, MethodDeclaration methdecl) {
934    // System.out.println("isImplementation(" + c.getName() + ", " + getName(methdecl) + ")");
935    Method meth = getMethod(c, methdecl);
936    if (meth != null) {
937      // System.out.println("isImplementation => true");
938      return true;
939    }
940    Class<?>[] interfaces = c.getInterfaces();
941    for (int i = 0; i < interfaces.length; i++) {
942      if (isImplementation(interfaces[i], methdecl)) {
943        return true;
944      }
945    }
946    return false;
947  }
948
949  // ///////////////////////////////////////////////////////////////////////////
950  // Etc.
951  //
952
953  // Following the chain of parent pointers from the child, returns
954  // the first node of the specified type or a subtype.  Returns null
955  // if no parent of that type.
956  public static @Nullable Node getParent(Class<?> type, Node child) {
957    Node currentNode = child.getParent();
958    while (true) {
959      if (type.isInstance(currentNode)) {
960        return currentNode;
961      } else {
962        if (currentNode == null) {
963          return null;
964        }
965        currentNode = currentNode.getParent();
966      }
967    }
968  }
969
970  public static void addDeclaration(ClassOrInterfaceBody c, ClassOrInterfaceBodyDeclaration d) {
971    c.f1.addNode(d);
972  }
973
974  // The "access" argument should be one of "public", "protected", or "private".
975  @SuppressWarnings("JdkObsolete") // JTB uses Enumeration
976  public static void setAccess(MethodDeclaration m, String access) {
977    // The following four confusing lines are a following of the
978    // syntax tree to get to the modifiers.
979    ClassOrInterfaceBodyDeclaration decl =
980        (ClassOrInterfaceBodyDeclaration) Ast.getParent(ClassOrInterfaceBodyDeclaration.class, m);
981    NodeChoice nc = decl.f0;
982    NodeSequence sequence = (NodeSequence) nc.choice;
983    Modifiers modifiers = (Modifiers) sequence.elementAt(0);
984    NodeListOptional options = modifiers.f0;
985    for (Enumeration e = options.elements(); e.hasMoreElements(); ) { // non-generic due to JTB
986      NodeChoice c = (NodeChoice) e.nextElement();
987
988      if (c.choice instanceof NodeToken) {
989        NodeToken t = (NodeToken) c.choice;
990        String token = t.tokenImage;
991        if (token.equals("public") || token.equals("protected") || token.equals("private")) {
992          t.tokenImage = access;
993          return;
994        }
995      }
996    }
997    // The method did not have any modifier
998    NodeToken t = new NodeToken(access);
999    NodeChoice c = new NodeChoice(t);
1000    options.addNode(c);
1001  }
1002
1003  @SuppressWarnings("JdkObsolete") // JTB uses Enumeration
1004  public static void removeMethodDeclAnnotations(MethodDeclaration method) {
1005    // The following four confusing lines are a following of the
1006    // syntax tree to get to the modifiers.
1007    ClassOrInterfaceBodyDeclaration decl =
1008        (ClassOrInterfaceBodyDeclaration)
1009            Ast.getParent(ClassOrInterfaceBodyDeclaration.class, method);
1010    NodeChoice nc = decl.f0;
1011    NodeSequence sequence = (NodeSequence) nc.choice;
1012    Modifiers modifiers = (Modifiers) sequence.elementAt(0);
1013    NodeListOptional options = modifiers.f0;
1014
1015    NodeListOptional filteredOptions = new NodeListOptional();
1016
1017    for (Enumeration e = options.elements(); e.hasMoreElements(); ) { // non-generic due to JTB
1018      NodeChoice c = (NodeChoice) e.nextElement();
1019
1020      if (!(c.choice instanceof jtb.syntaxtree.Annotation)) {
1021        filteredOptions.addNode(c);
1022      }
1023    }
1024
1025    modifiers.f0 = filteredOptions;
1026  }
1027
1028  // returns true if, for some node in the tree, node.tokenImage.equals(s)
1029  public static boolean contains(Node n, String s) {
1030    class ContainsVisitor extends DepthFirstVisitor {
1031      boolean found = false;
1032      private final String s;
1033
1034      ContainsVisitor(String s) {
1035        this.s = s;
1036      }
1037
1038      @Override
1039      public void visit(NodeToken node) {
1040        found = found || s.equals(node.tokenImage);
1041      }
1042    }
1043    ContainsVisitor cv = new ContainsVisitor(s);
1044    n.accept(cv);
1045    return cv.found;
1046  }
1047
1048  // Body must begin and end with a brace.
1049  public static void setBody(MethodDeclaration m, String body) {
1050    m.f4.choice = (Block) Ast.create("Block", body);
1051  }
1052
1053  // Returns the body of a method, including the leading "{" and trailing "}"
1054  public static String getBody(MethodDeclaration m) {
1055    return format(m.f4.choice);
1056  }
1057
1058  public static String getReturnType(MethodDeclaration m) {
1059    Node n = m.f1.f0.choice;
1060    return format(n);
1061  }
1062
1063  public static String getMethodDeclarator(MethodDeclaration m) {
1064    MethodDeclarator d = m.f2;
1065    return format(d);
1066  }
1067
1068  // Returns the parameters of the method, as a list of
1069  // FormalParameter objects.  Returns an empty list if there are no
1070  // parameters.
1071  public static List<FormalParameter> getParameters(MethodDeclaration m) {
1072    class GetParametersVisitor extends DepthFirstVisitor {
1073      List<FormalParameter> parameters = new ArrayList<>();
1074
1075      @Override
1076      public void visit(FormalParameter p) {
1077        parameters.add(p);
1078      }
1079    }
1080    GetParametersVisitor v = new GetParametersVisitor();
1081    MethodDeclarator d = m.f2;
1082    d.accept(v);
1083    return v.parameters;
1084  }
1085
1086  // Returns the parameters of the constructor, as a list of
1087  // FormalParameter objects. Does not include implicit parameters for
1088  // inner classes.
1089  public static List<FormalParameter> getParametersNoImplicit(ConstructorDeclaration cd) {
1090    class GetParametersVisitor extends DepthFirstVisitor {
1091      List<FormalParameter> parameters = new ArrayList<>();
1092
1093      @Override
1094      public void visit(FormalParameter p) {
1095        parameters.add(p);
1096      }
1097    }
1098    GetParametersVisitor v = new GetParametersVisitor();
1099    FormalParameters fp = cd.f2;
1100    fp.accept(v);
1101    return v.parameters;
1102  }
1103
1104  // Returns the parameters of the constructor, as a list of
1105  // FormalParameter objects.  Returns an empty list if there are no
1106  // parameters.
1107  public static List<FormalParameter> getParameters(ConstructorDeclaration cd) {
1108    class GetParametersVisitor extends DepthFirstVisitor {
1109      List<FormalParameter> parameters = new ArrayList<>();
1110
1111      @Override
1112      public void visit(FormalParameter p) {
1113        parameters.add(p);
1114      }
1115    }
1116    GetParametersVisitor v = new GetParametersVisitor();
1117    FormalParameters fp = cd.f2;
1118    fp.accept(v);
1119
1120    // Inner class constructors have implicit outer class parameter, which had
1121    // caused the constructor signatures not to match (and thus invariants would
1122    // not merge into inner class constructors)
1123
1124    // Look into replacing getClass because that requires that the compiled class
1125    // be in the classpath
1126
1127    Node innerClassNode = getParent(ClassOrInterfaceDeclaration.class, cd);
1128    Node outerClassNode = getParent(ClassOrInterfaceDeclaration.class, innerClassNode);
1129
1130    //     boolean isNestedStatic = false;
1131    //     Node nestedMaybe = innerClassNode.getParent();
1132    //     if (nestedMaybe instanceof NestedClassDeclaration) {
1133    //       if (isStatic((NestedClassDeclaration)nestedMaybe)) {
1134    //         isNestedStatic = true;
1135    //       }
1136    //     }
1137
1138    if (isInner((ClassOrInterfaceDeclaration) innerClassNode)
1139        && !isStatic((ClassOrInterfaceDeclaration) innerClassNode)) {
1140      NodeToken classNameToken = ((ClassOrInterfaceDeclaration) outerClassNode).f1;
1141      ClassOrInterfaceType name =
1142          new ClassOrInterfaceType(classNameToken, new NodeOptional(), new NodeListOptional());
1143      NodeSequence n10 = new NodeSequence(name);
1144      NodeSequence n9 = new NodeSequence(n10);
1145      n9.addNode(new NodeListOptional());
1146      ReferenceType refType = new ReferenceType(new NodeChoice(n9, 1));
1147      jtb.syntaxtree.Type type = new jtb.syntaxtree.Type(new NodeChoice(refType, 1));
1148      // Setting all the line and column info on the NodeTokens is a hassle.
1149      // So we sidestep and 'cheat' by putting a blank as the first character of the name.
1150      // This works because we are going to re-parse it in the node Create method.
1151      VariableDeclaratorId dummyOuterParamName =
1152          new VariableDeclaratorId(new NodeToken(" outer$this"), new NodeListOptional());
1153      FormalParameter implicitOuter =
1154          new FormalParameter(
1155              new Modifiers(new NodeListOptional()),
1156              new NodeOptional(),
1157              type,
1158              new NodeOptional(),
1159              dummyOuterParamName);
1160      v.parameters.add(0, implicitOuter);
1161    }
1162
1163    return v.parameters;
1164  }
1165
1166  public static void addImport(CompilationUnit u, ImportDeclaration i) {
1167    u.f1.addNode(i);
1168  }
1169
1170  // Returns a list of Strings, the names of all the variables in the node.
1171  // The node is an expression, conditional expression, or primary
1172  // expression.
1173  @SuppressWarnings("JdkObsolete") // JTB uses Enumeration
1174  public static Set<String> getVariableNames(Node expr) {
1175
1176    class GetSymbolNamesVisitor extends DepthFirstVisitor {
1177      Set<String> symbolNames = new HashSet<>();
1178
1179      @Override
1180      public void visit(Name n) {
1181        Node gp = n.getParent().getParent();
1182        if (gp instanceof PrimaryPrefix) {
1183          PrimaryExpression ggp = (PrimaryExpression) gp.getParent();
1184          for (Enumeration e = getPrimarySuffixes(ggp); e.hasMoreElements(); ) {
1185            PrimarySuffix s = (PrimarySuffix) e.nextElement();
1186            if (s.f0.choice instanceof Arguments) {
1187              return;
1188            }
1189          }
1190          symbolNames.add(format(n));
1191        }
1192      }
1193    }
1194
1195    GetSymbolNamesVisitor v = new GetSymbolNamesVisitor();
1196    expr.accept(v);
1197    return v.symbolNames;
1198  }
1199
1200  /**
1201   * Returns an Enumeration of PrimarySuffix objects (but the static type of the elements is only
1202   * known to be Node).
1203   */
1204  public static Enumeration getPrimarySuffixes(PrimaryExpression p) {
1205    return p.f1.elements();
1206  }
1207
1208  // Return true if the strings are equal, or if abbreviated is a suffix
1209  // of goal.  This wouldn't be necessary if we did full type resolution.
1210  static boolean typeMatch(String pptTypeString, String astTypeString) {
1211    // System.out.println("Comparing " + pptTypeString + " to " + astTypeString);
1212    if (astTypeString.equals(pptTypeString)) {
1213      return true;
1214    }
1215    // If astTypeString is missing the leading package name, permit a match
1216    if (pptTypeString.endsWith(astTypeString)
1217        && (pptTypeString.charAt(pptTypeString.length() - astTypeString.length() - 1) == '.')) {
1218      return true;
1219    }
1220    return false;
1221  }
1222
1223  /** Returns true if this is the main method for this class. */
1224  public static boolean isMain(MethodDeclaration md) {
1225    if (Ast.getName(md).equals("main")) {
1226      List<FormalParameter> params = Ast.getParameters(md);
1227      if (params.size() == 1) {
1228        FormalParameter fp = params.get(0);
1229        String paramtype = Ast.getType(fp);
1230        if (Ast.typeMatch("java.lang.String[]", paramtype)) {
1231          return true;
1232        }
1233      }
1234    }
1235    return false;
1236  }
1237
1238  /**
1239   * This code is taken from and modified from daikon.PrintInvariants.print_invariants. The main
1240   * modification is that instead of printing the invariants, we return a list of them.
1241   * modifications involve removing code that I don't need here, like printing of debugging info.
1242   *
1243   * <p>[[ TODO: instead of duplicating code, you should add this method to PrintInvariants (or
1244   * wherever it belongs) and have PrintInvariants.print_invariants call it. ]]
1245   */
1246  public static List<Invariant> getInvariants(PptTopLevel ppt, PptMap ppt_map) {
1247
1248    // make names easier to read before printing
1249    ppt.simplify_variable_names();
1250
1251    // I could instead sort the PptSlice objects, then sort the invariants
1252    // in each PptSlice.  That would be more efficient, but this is
1253    // probably not a bottleneck anyway.
1254    List<Invariant> invs_vector = new ArrayList<>(ppt.getInvariants());
1255
1256    Invariant[] invs_array = invs_vector.toArray(new Invariant[0]);
1257    Arrays.sort(invs_array, PptTopLevel.icfp);
1258
1259    Global.non_falsified_invariants += invs_array.length;
1260
1261    List<Invariant> accepted_invariants = new ArrayList<>();
1262
1263    for (int i = 0; i < invs_array.length; i++) {
1264      Invariant inv = invs_array[i];
1265
1266      assert !(inv instanceof Equality);
1267      for (int j = 0; j < inv.ppt.var_infos.length; j++) {
1268        assert !inv.ppt.var_infos[j].missingOutOfBounds()
1269            : "var '" + inv.ppt.var_infos[j].name() + "' out of bounds in " + inv.format();
1270      }
1271      InvariantFilters fi = InvariantFilters.defaultFilters();
1272
1273      boolean fi_accepted = true;
1274      InvariantFilter filter_result = fi.shouldKeep(inv);
1275      fi_accepted = (filter_result == null);
1276
1277      // Never print the guarding predicates themselves, they should only
1278      // print as part of GuardingImplications
1279      if (fi_accepted && !inv.isGuardingPredicate) {
1280        Global.reported_invariants++;
1281        accepted_invariants.add(inv);
1282      }
1283    }
1284
1285    accepted_invariants = InvariantFilters.addEqualityInvariants(accepted_invariants);
1286
1287    return accepted_invariants;
1288  }
1289
1290  public static boolean isStatic(ClassOrInterfaceDeclaration n) {
1291    return isStaticInternal((Node) n);
1292  }
1293
1294  public static boolean isStatic(MethodDeclaration n) {
1295    return isStaticInternal((Node) n);
1296  }
1297
1298  public static Modifiers getModifiers(ClassOrInterfaceDeclaration n) {
1299    return getModifiersInternal((Node) n);
1300  }
1301
1302  public static Modifiers getModifiers(MethodDeclaration n) {
1303    return getModifiersInternal((Node) n);
1304  }
1305
1306  private static Modifiers getModifiersInternal(Node n) {
1307
1308    assert (n instanceof MethodDeclaration) || (n instanceof ClassOrInterfaceDeclaration);
1309
1310    ClassOrInterfaceBodyDeclaration decl =
1311        (ClassOrInterfaceBodyDeclaration) getParent(ClassOrInterfaceBodyDeclaration.class, n);
1312    assert decl != null;
1313    NodeSequence seq = (NodeSequence) decl.f0.choice;
1314    return (Modifiers) seq.elementAt(0);
1315  }
1316
1317  private static boolean isStaticInternal(Node n) {
1318    // Grammar production for ClassOrInterfaceBodyDeclaration
1319    // f0 -> Initializer()
1320    //       | Modifiers() ( ClassOrInterfaceDeclaration(modifiers) | EnumDeclaration(modifiers) |
1321    // ConstructorDeclaration() | FieldDeclaration(modifiers) | MethodDeclaration(modifiers) )
1322    //       | ";"
1323    assert (n instanceof MethodDeclaration) || (n instanceof ClassOrInterfaceDeclaration);
1324
1325    Modifiers modifiers = getModifiersInternal(n);
1326    if (modifierPresent(modifiers, "static")) {
1327      return true;
1328    } else {
1329      return false;
1330    }
1331  }
1332
1333  public static boolean modifierPresent(Modifiers modifiers, String modifierString) {
1334    // Grammar production:
1335    // f0 -> ( ( "public" | "static" | "protected" | "private" | "final" | "abstract" |
1336    // "synchronized" | "native" | "transient" | "volatile" | "strictfp" | Annotation() ) )*
1337
1338    NodeListOptional list = modifiers.f0;
1339    for (int i = 0; i < list.size(); i++) {
1340      NodeChoice nodeChoice = (NodeChoice) list.elementAt(i);
1341
1342      if (nodeChoice.choice instanceof NodeToken) {
1343        NodeToken keyword = (NodeToken) nodeChoice.choice;
1344        if (keyword.toString().equals(modifierString)) {
1345          return true;
1346        }
1347      }
1348    }
1349    return false;
1350  }
1351
1352  public static boolean isInner(ClassOrInterfaceDeclaration n) {
1353    if (getParent(ClassOrInterfaceDeclaration.class, n) != null) {
1354      return true;
1355    } else {
1356      return false;
1357    }
1358  }
1359
1360  public static boolean isInAnonymousClass(Node node) {
1361    ClassOrInterfaceBody clsbody =
1362        (ClassOrInterfaceBody) Ast.getParent(ClassOrInterfaceBody.class, node);
1363    return !(clsbody.getParent() instanceof ClassOrInterfaceDeclaration);
1364  }
1365
1366  public static boolean isInterface(ClassOrInterfaceBody n) {
1367    // n.getParent won't be an ClassOrInterfaceDeclaration for anonymous
1368    // class bodies such as in  new List() { ... }  -- anonymous classes can
1369    // never be interfaces, however, so that's simple enough to handle. :)
1370    if (!(n.getParent() instanceof ClassOrInterfaceDeclaration)) {
1371      return false;
1372    }
1373
1374    return isInterface((ClassOrInterfaceDeclaration) n.getParent());
1375  }
1376
1377  public static boolean isInterface(ClassOrInterfaceDeclaration n) {
1378    // Grammar production for ClassOrInterfaceDeclaration:
1379    // f0 -> ( "class" | "interface" )
1380    // f1 -> <IDENTIFIER>
1381    // f2 -> [ TypeParameters() ]
1382    // f3 -> [ ExtendsList(isInterface) ]
1383    // f4 -> [ ImplementsList(isInterface) ]
1384    // f5 -> ClassOrInterfaceBody(isInterface)
1385    NodeToken t = (NodeToken) n.f0.choice;
1386    String token = t.tokenImage;
1387    if (token.equals("interface")) {
1388      return true;
1389    } else {
1390      return false;
1391    }
1392  }
1393
1394  public static boolean isPrimitive(jtb.syntaxtree.Type n) {
1395    // Grammar production:
1396    // f0 -> ReferenceType()
1397    //       | PrimitiveType()
1398    return (n.f0.choice instanceof PrimitiveType);
1399  }
1400
1401  public static boolean isReference(jtb.syntaxtree.Type n) {
1402    // Grammar production:
1403    // f0 -> ReferenceType()
1404    //       | PrimitiveType()
1405    return (n.f0.choice instanceof ReferenceType);
1406  }
1407
1408  public static boolean isArray(jtb.syntaxtree.Type n) {
1409    // Grammar production:
1410    // f0 -> PrimitiveType() ( "[" "]" )+
1411    //       | ( ClassOrInterfaceType() ) ( "[" "]" )*
1412    if (isPrimitive(n)) {
1413      return false;
1414    }
1415    assert isReference(n);
1416    ReferenceType refType = (ReferenceType) n.f0.choice;
1417    NodeSequence seq = (NodeSequence) refType.f0.choice;
1418    if (seq.elementAt(0) instanceof PrimitiveType) {
1419      return true; // see grammar--must be array in this case
1420    }
1421    NodeListOptional opt = (NodeListOptional) seq.elementAt(1);
1422    return opt.present();
1423  }
1424
1425  public static String classnameForSourceOutput(Class<?> c) {
1426
1427    assert !c.equals(Void.TYPE);
1428
1429    if (c.isPrimitive()) {
1430      return c.getName();
1431    } else if (c.isArray()) {
1432      return Signatures.classGetNameToBinaryName(c.getName());
1433    } else {
1434      return c.getName();
1435    }
1436  }
1437}