    1   package org.codehaus.groovy.classgen;
    2
    3   import org.codehaus.groovy.ast.*;
    4   import org.codehaus.groovy.control.SourceUnit;
    5
    6   import java.io.PrintWriter;
    7   import java.io.StringWriter;
    8
    9   /***
    10   *
    11   * @author Paul King
    12   */
    13  public class ClassCompletionVerifierTest extends TestSupport {
    14      private SourceUnit source;
    15      private ClassCompletionVerifier verifier;
    16      private static final String ABSTRACT_FINAL_CLASS = "AbstractFinalClass";
    17      private static final String FINAL_INTERFACE = "FinalInterface";
    18      private static final String EXPECTED_CLASS_MODIFIER_ERROR_MESSAGE =
    19              "The class '" + ABSTRACT_FINAL_CLASS + "' must not be both final and abstract.";
    20      private static final String EXPECTED_INTERFACE_MODIFIER_ERROR_MESSAGE =
    21              "The interface '" + FINAL_INTERFACE + "' must not be final. It is by definition abstract.";
    22      private static final String EXPECTED_INTERFACE_FINAL_METHOD_ERROR_MESSAGE =
    23              "The method 'xxx' from interface 'zzz' must not be final. It is by definition abstract.";
    24      private static final String EXPECTED_INTERFACE_STATIC_METHOD_ERROR_MESSAGE =
    25              "The method 'yyy' from interface 'zzz' must not be static. Only fields may be static in an interface.";
    26      private static final String EXPECTED_TRANSIENT_CLASS_ERROR_MESSAGE =
    27              "The class 'DodgyClass' has an incorrect modifier transient.";
    28      private static final String EXPECTED_VOLATILE_CLASS_ERROR_MESSAGE =
    29              "The class 'DodgyClass' has an incorrect modifier volatile.";
    30      private static final String EXPECTED_DUPLICATE_METHOD_ERROR_CLASS_MESSAGE =
    31              "Repetitive method name/signature for method 'xxx' in class 'zzz'.";
    32      private static final String EXPECTED_DUPLICATE_METHOD_ERROR_INTERFACE_MESSAGE =
    33              "Repetitive method name/signature for method 'xxx' in interface 'zzz'.";
    34
    35      protected void setUp() throws Exception {
    36          super.setUp();
    37          source = SourceUnit.create("dummy.groovy", "");
    38          verifier = new ClassCompletionVerifier(source);
    39      }
    40
    41      public void testDetectsFinalAbstractClass() throws Exception {
    42          checkVisitErrors("FinalClass", ACC_FINAL, false);
    43          checkVisitErrors("AbstractClass", ACC_ABSTRACT, false);
    44          checkVisitErrors(ABSTRACT_FINAL_CLASS, ACC_ABSTRACT | ACC_FINAL, true);
    45          checkErrorMessage(EXPECTED_CLASS_MODIFIER_ERROR_MESSAGE);
    46      }
    47
    48      public void testDetectsDuplicateMethodsForClassNoParams() throws Exception {
    49          checkDetectsDuplicateMethods(0, EXPECTED_DUPLICATE_METHOD_ERROR_CLASS_MESSAGE, Parameter.EMPTY_ARRAY);
    50      }
    51
    52      public void testDetectsDuplicateMethodsForInterfaceOneParam() throws Exception {
    53          Parameter[] stringParam = { new Parameter(ClassHelper.STRING_TYPE, "x") };
    54          checkDetectsDuplicateMethods(ACC_INTERFACE, EXPECTED_DUPLICATE_METHOD_ERROR_INTERFACE_MESSAGE, stringParam);
    55      }
    56
    57      private void checkDetectsDuplicateMethods(int modifiers, String expectedErrorMessage, Parameter[] params) {
    58          ClassNode node = new ClassNode("zzz", modifiers, ClassHelper.OBJECT_TYPE);
    59          node.addMethod(new MethodNode("xxx", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, params, ClassNode.EMPTY_ARRAY, null));
    60          node.addMethod(new MethodNode("xxx", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, params, ClassNode.EMPTY_ARRAY, null));
    61          verifier.visitClass(node);
    62          checkErrorCount(2);
    63          checkErrorMessage(expectedErrorMessage);
    64      }
    65
    66      public void testDetectsIncorrectOtherModifier() throws Exception {
    67          checkVisitErrors("DodgyClass", ACC_TRANSIENT | ACC_VOLATILE, true);
    68          checkErrorMessage(EXPECTED_TRANSIENT_CLASS_ERROR_MESSAGE);
    69          checkErrorMessage(EXPECTED_VOLATILE_CLASS_ERROR_MESSAGE);
    70      }
    71
    72      public void testDetectsFinalAbstractInterface() throws Exception {
    73          checkVisitErrors(FINAL_INTERFACE, ACC_ABSTRACT | ACC_FINAL | ACC_INTERFACE, true);
    74          checkErrorMessage(EXPECTED_INTERFACE_MODIFIER_ERROR_MESSAGE);
    75      }
    76
    77      public void testDetectsFinalAndStaticMethodsInInterface() throws Exception {
    78          ClassNode node = new ClassNode("zzz", ACC_ABSTRACT | ACC_INTERFACE, ClassHelper.OBJECT_TYPE);
    79          node.addMethod(new MethodNode("xxx", ACC_PUBLIC | ACC_FINAL, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null));
    80          node.addMethod(new MethodNode("yyy", ACC_PUBLIC | ACC_STATIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null));
    81          // constructors should not be treated as errors (they have no real meaning for interfaces anyway)
    82          node.addMethod(new MethodNode("<clinit>", ACC_PUBLIC | ACC_STATIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null));
    83          verifier.visitClass(node);
    84          checkErrorCount(2);
    85          checkErrorMessage(EXPECTED_INTERFACE_FINAL_METHOD_ERROR_MESSAGE);
    86          checkErrorMessage(EXPECTED_INTERFACE_STATIC_METHOD_ERROR_MESSAGE);
    87      }
    88
    89      private void checkErrorCount(int count) {
    90          assertEquals(buildErrorMessage(count), count, source.getErrorCollector().getErrorCount());
    91      }
    92
    93      private String buildErrorMessage(int count) {
    94          StringBuffer sb = new StringBuffer();
    95          sb.append("Expected ").append(count);
    96          sb.append(" error messages but found ");
    97          sb.append(source.getErrorCollector().getErrorCount()).append(":\n");
    98          sb.append(flattenErrorMessage());
    99          return sb.toString();
    100     }
    101
    102     private void checkVisitErrors(String name, int modifiers, boolean expectedToFail) {
    103         ClassNode node = new ClassNode(name, modifiers, ClassHelper.OBJECT_TYPE);
    104         verifier.visitClass(node);
    105         assertTrue(source.getErrorCollector().hasErrors() == expectedToFail);
    106     }
    107
    108     private void checkErrorMessage(String expectedErrorMessage) {
    109         assertTrue("Expected an error message but none found.", source.getErrorCollector().hasErrors());
    110         assertTrue("Expected message to contain <" + expectedErrorMessage +
    111                 "> but was <" + flattenErrorMessage() + ">.",
    112                 flattenErrorMessage().indexOf(expectedErrorMessage) != -1);
    113     }
    114
    115     private String flattenErrorMessage() {
    116         StringWriter stringWriter = new StringWriter();
    117         PrintWriter writer = new PrintWriter(stringWriter, true);
    118         for (int i = source.getErrorCollector().getErrorCount() - 1; i >= 0; i--) {
    119             source.getErrorCollector().getError(i).write(writer);
    120         }
    121         writer.close();
    122         return stringWriter.toString();
    123     }
    124 }