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