001: /*******************************************************************************
002: * Copyright (c) 2004 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Common Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/cpl-v10.html
007: *
008: * Contributors:
009: * IBM - Initial API and implementation
010: * Groovy community - subsequent modifications
011: ******************************************************************************/package org.codehaus.groovy.classgen;
012:
013: import java.lang.reflect.Modifier;
014: import java.util.Iterator;
015: import java.util.List;
016:
017: import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
018: import org.codehaus.groovy.ast.ClassHelper;
019: import org.codehaus.groovy.ast.ClassNode;
020: import org.codehaus.groovy.ast.FieldNode;
021: import org.codehaus.groovy.ast.MethodNode;
022: import org.codehaus.groovy.ast.Parameter;
023: import org.codehaus.groovy.ast.expr.BinaryExpression;
024: import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
025: import org.codehaus.groovy.ast.expr.MapEntryExpression;
026: import org.codehaus.groovy.ast.stmt.CatchStatement;
027: import org.codehaus.groovy.control.SourceUnit;
028: import org.objectweb.asm.Opcodes;
029: import org.codehaus.groovy.syntax.Types;
030:
031: /**
032: * ClassCompletionVerifier
033: */
034: public class ClassCompletionVerifier extends ClassCodeVisitorSupport {
035:
036: private ClassNode currentClass;
037: private SourceUnit source;
038:
039: public ClassCompletionVerifier(SourceUnit source) {
040: this .source = source;
041: }
042:
043: public ClassNode getClassNode() {
044: return currentClass;
045: }
046:
047: public void visitClass(ClassNode node) {
048: ClassNode oldClass = currentClass;
049: currentClass = node;
050: checkImplementsAndExtends(node);
051: if (source != null && !source.getErrorCollector().hasErrors()) {
052: checkClassForIncorrectModifiers(node);
053: checkClassForOverwritingFinal(node);
054: checkMethodsForIncorrectModifiers(node);
055: checkMethodsForOverwritingFinal(node);
056: checkNoAbstractMethodsNonabstractClass(node);
057: }
058: super .visitClass(node);
059: currentClass = oldClass;
060: }
061:
062: private void checkNoAbstractMethodsNonabstractClass(ClassNode node) {
063: if (Modifier.isAbstract(node.getModifiers()))
064: return;
065: List abstractMethods = node.getAbstractMethods();
066: if (abstractMethods == null)
067: return;
068: for (Iterator iter = abstractMethods.iterator(); iter.hasNext();) {
069: MethodNode method = (MethodNode) iter.next();
070: String methodName = method.getTypeDescriptor();
071: addError(
072: "Can't have an abstract method in a non-abstract class."
073: + " The " + getDescription(node)
074: + " must be declared abstract or" + " the "
075: + getDescription(method)
076: + " must be implemented.", node);
077: }
078: }
079:
080: private void checkClassForIncorrectModifiers(ClassNode node) {
081: checkClassForAbstractAndFinal(node);
082: checkClassForOtherModifiers(node);
083: }
084:
085: private void checkClassForAbstractAndFinal(ClassNode node) {
086: if (!Modifier.isAbstract(node.getModifiers()))
087: return;
088: if (!Modifier.isFinal(node.getModifiers()))
089: return;
090: if (node.isInterface()) {
091: addError(
092: "The "
093: + getDescription(node)
094: + " must not be final. It is by definition abstract.",
095: node);
096: } else {
097: addError("The " + getDescription(node)
098: + " must not be both final and abstract.", node);
099: }
100: }
101:
102: private void checkClassForOtherModifiers(ClassNode node) {
103: // TODO: work out why "synchronised" can't be used here
104: checkClassForModifier(node, Modifier.isTransient(node
105: .getModifiers()), "transient");
106: checkClassForModifier(node, Modifier.isVolatile(node
107: .getModifiers()), "volatile");
108: }
109:
110: private void checkClassForModifier(ClassNode node,
111: boolean condition, String modifierName) {
112: if (!condition)
113: return;
114: addError("The " + getDescription(node)
115: + " has an incorrect modifier " + modifierName + ".",
116: node);
117: }
118:
119: private String getDescription(ClassNode node) {
120: return (node.isInterface() ? "interface" : "class") + " '"
121: + node.getName() + "'";
122: }
123:
124: private String getDescription(MethodNode node) {
125: return "method '" + node.getName() + "'";
126: }
127:
128: private String getDescription(FieldNode node) {
129: return "field '" + node.getName() + "'";
130: }
131:
132: private void checkAbstractDeclaration(MethodNode methodNode) {
133: if (!Modifier.isAbstract(methodNode.getModifiers()))
134: return;
135: if (Modifier.isAbstract(currentClass.getModifiers()))
136: return;
137: addError(
138: "Can't have an abstract method in a non-abstract class."
139: + " The " + getDescription(currentClass)
140: + " must be declared abstract or the method '"
141: + methodNode.getTypeDescriptor()
142: + "' must not be abstract.", methodNode);
143: }
144:
145: private void checkClassForOverwritingFinal(ClassNode cn) {
146: ClassNode super CN = cn.getSuperClass();
147: if (super CN == null)
148: return;
149: if (!Modifier.isFinal(super CN.getModifiers()))
150: return;
151: StringBuffer msg = new StringBuffer();
152: msg.append("You are not allowed to overwrite the final ");
153: msg.append(getDescription(super CN));
154: msg.append(".");
155: addError(msg.toString(), cn);
156: }
157:
158: private void checkImplementsAndExtends(ClassNode node) {
159: ClassNode cn = node.getSuperClass();
160: if (cn.isInterface() && !node.isInterface()) {
161: addError("You are not allowed to extend the "
162: + getDescription(cn) + ", use implements instead.",
163: node);
164: }
165: ClassNode[] interfaces = node.getInterfaces();
166: for (int i = 0; i < interfaces.length; i++) {
167: cn = interfaces[i];
168: if (!cn.isInterface()) {
169: addError(
170: "You are not allowed to implement the "
171: + getDescription(cn)
172: + ", use extends instead.", node);
173: }
174: }
175: }
176:
177: private void checkMethodsForIncorrectModifiers(ClassNode cn) {
178: if (!cn.isInterface())
179: return;
180: List methods = cn.getMethods();
181: for (Iterator cnIter = methods.iterator(); cnIter.hasNext();) {
182: MethodNode method = (MethodNode) cnIter.next();
183: if (Modifier.isFinal(method.getModifiers())) {
184: addError(
185: "The "
186: + getDescription(method)
187: + " from "
188: + getDescription(cn)
189: + " must not be final. It is by definition abstract.",
190: method);
191: }
192: if (Modifier.isStatic(method.getModifiers())
193: && !isConstructor(method)) {
194: addError(
195: "The "
196: + getDescription(method)
197: + " from "
198: + getDescription(cn)
199: + " must not be static. Only fields may be static in an interface.",
200: method);
201: }
202: }
203: }
204:
205: private boolean isConstructor(MethodNode method) {
206: return method.getName().equals("<clinit>");
207: }
208:
209: private void checkMethodsForOverwritingFinal(ClassNode cn) {
210: List methods = cn.getMethods();
211: for (Iterator cnIter = methods.iterator(); cnIter.hasNext();) {
212: MethodNode method = (MethodNode) cnIter.next();
213: Parameter[] params = method.getParameters();
214: for (ClassNode super CN = cn.getSuperClass(); super CN != null; super CN = super CN
215: .getSuperClass()) {
216: List super Methods = super CN
217: .getMethods(method.getName());
218: for (Iterator iter = super Methods.iterator(); iter
219: .hasNext();) {
220: MethodNode super Method = (MethodNode) iter.next();
221: Parameter[] super Params = super Method
222: .getParameters();
223: if (!hasEqualParameterTypes(params, super Params))
224: continue;
225: if (!Modifier.isFinal(super Method.getModifiers()))
226: return;
227: addInvalidUseOfFinalError(method, params, super CN);
228: return;
229: }
230: }
231: }
232: }
233:
234: private void addInvalidUseOfFinalError(MethodNode method,
235: Parameter[] parameters, ClassNode super CN) {
236: StringBuffer msg = new StringBuffer();
237: msg
238: .append(
239: "You are not allowed to overwrite the final method ")
240: .append(method.getName());
241: msg.append("(");
242: boolean needsComma = false;
243: for (int i = 0; i < parameters.length; i++) {
244: if (needsComma) {
245: msg.append(",");
246: } else {
247: needsComma = true;
248: }
249: msg.append(parameters[i].getType());
250: }
251: msg.append(") from ").append(getDescription(super CN));
252: msg.append(".");
253: addError(msg.toString(), method);
254: }
255:
256: private boolean hasEqualParameterTypes(Parameter[] first,
257: Parameter[] second) {
258: if (first.length != second.length)
259: return false;
260: for (int i = 0; i < first.length; i++) {
261: String ft = first[i].getType().getName();
262: String st = second[i].getType().getName();
263: if (ft.equals(st))
264: continue;
265: return false;
266: }
267: return true;
268: }
269:
270: protected SourceUnit getSourceUnit() {
271: return source;
272: }
273:
274: public void visitConstructorCallExpression(
275: ConstructorCallExpression call) {
276: ClassNode type = call.getType();
277: if (Modifier.isAbstract(type.getModifiers())) {
278: addError("You cannot create an instance from the abstract "
279: + getDescription(type) + ".", call);
280: }
281: super .visitConstructorCallExpression(call);
282: }
283:
284: public void visitMethod(MethodNode node) {
285: checkAbstractDeclaration(node);
286: checkRepetitiveMethod(node);
287: checkOverloadingPrivateAndPublic(node);
288: super .visitMethod(node);
289: }
290:
291: private void checkOverloadingPrivateAndPublic(MethodNode node) {
292: if (isConstructor(node))
293: return;
294: List methods = currentClass.getMethods(node.getName());
295: boolean hasPrivate = false;
296: boolean hasPublic = false;
297: for (Iterator iter = methods.iterator(); iter.hasNext();) {
298: MethodNode element = (MethodNode) iter.next();
299: if (element == node)
300: continue;
301: int modifiers = element.getModifiers();
302: if (Modifier.isPublic(modifiers)
303: || Modifier.isProtected(modifiers)) {
304: hasPublic = true;
305: } else {
306: hasPrivate = true;
307: }
308: }
309: if (hasPrivate && hasPublic) {
310: addError(
311: "Mixing private and public/protected methods of the same name causes multimethods to be disabled and is forbidden to avoid surprising behaviour. Renaming the private methods will solve the problem.",
312: node);
313: }
314: }
315:
316: private void checkRepetitiveMethod(MethodNode node) {
317: if (isConstructor(node))
318: return;
319: List methods = currentClass.getMethods(node.getName());
320: for (Iterator iter = methods.iterator(); iter.hasNext();) {
321: MethodNode element = (MethodNode) iter.next();
322: if (element == node)
323: continue;
324: if (!element.getDeclaringClass().equals(
325: node.getDeclaringClass()))
326: continue;
327: Parameter[] p1 = node.getParameters();
328: Parameter[] p2 = element.getParameters();
329: if (p1.length != p2.length)
330: continue;
331: addErrorIfParamsAndReturnTypeEqual(p2, p1, node, element);
332: }
333: }
334:
335: private void addErrorIfParamsAndReturnTypeEqual(Parameter[] p2,
336: Parameter[] p1, MethodNode node, MethodNode element) {
337: boolean isEqual = true;
338: for (int i = 0; i < p2.length; i++) {
339: isEqual &= p1[i].getType().equals(p2[i].getType());
340: }
341: isEqual &= node.getReturnType().equals(element.getReturnType());
342: if (isEqual) {
343: addError("Repetitive method name/signature for "
344: + getDescription(node) + " in "
345: + getDescription(currentClass) + ".", node);
346: }
347: }
348:
349: public void visitField(FieldNode node) {
350: if (currentClass.getField(node.getName()) != node) {
351: addError("The " + getDescription(node)
352: + " is declared multiple times.", node);
353: }
354: checkInterfaceFieldModifiers(node);
355: super .visitField(node);
356: }
357:
358: private void checkInterfaceFieldModifiers(FieldNode node) {
359: if (!currentClass.isInterface())
360: return;
361: if ((node.getModifiers() & (Opcodes.ACC_PUBLIC
362: | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)) == 0) {
363: addError(
364: "The "
365: + getDescription(node)
366: + " is not 'public final static' but is defined in the "
367: + getDescription(currentClass) + ".", node);
368: }
369: }
370:
371: public void visitBinaryExpression(BinaryExpression expression) {
372: if (expression.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
373: && expression.getRightExpression() instanceof MapEntryExpression) {
374: addError(
375: "You tried to use a map entry for an index operation, this is not allowed. "
376: + "Maybe something should be set in parentheses or a comma is missing?",
377: expression.getRightExpression());
378: }
379: super .visitBinaryExpression(expression);
380: }
381:
382: public void visitCatchStatement(CatchStatement cs) {
383: if (!(cs.getExceptionType().isDerivedFrom(ClassHelper
384: .make(Throwable.class)))) {
385: addError(
386: "Catch statement parameter type is not a subclass of Throwable.",
387: cs);
388: }
389: super.visitCatchStatement(cs);
390: }
391: }
|