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
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 }