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