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 ******************************************************************************/
012 package org.codehaus.groovy.classgen;
013
014 import java.lang.reflect.Modifier;
015 import java.util.Iterator;
016 import java.util.List;
017
018 import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
019 import org.codehaus.groovy.ast.ClassHelper;
020 import org.codehaus.groovy.ast.ClassNode;
021 import org.codehaus.groovy.ast.FieldNode;
022 import org.codehaus.groovy.ast.MethodNode;
023 import org.codehaus.groovy.ast.Parameter;
024 import org.codehaus.groovy.ast.expr.BinaryExpression;
025 import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
026 import org.codehaus.groovy.ast.expr.MapEntryExpression;
027 import org.codehaus.groovy.ast.stmt.CatchStatement;
028 import org.codehaus.groovy.control.SourceUnit;
029 import org.objectweb.asm.Opcodes;
030 import org.codehaus.groovy.syntax.Types;
031
032 /**
033 * ClassCompletionVerifier
034 */
035 public class ClassCompletionVerifier extends ClassCodeVisitorSupport {
036
037 private ClassNode currentClass;
038 private SourceUnit source;
039
040 public ClassCompletionVerifier(SourceUnit source) {
041 this.source = source;
042 }
043
044 public ClassNode getClassNode() {
045 return currentClass;
046 }
047
048 public void visitClass(ClassNode node) {
049 ClassNode oldClass = currentClass;
050 currentClass = node;
051 checkImplementsAndExtends(node);
052 if (source != null && !source.getErrorCollector().hasErrors()) {
053 checkClassForIncorrectModifiers(node);
054 checkClassForOverwritingFinal(node);
055 checkMethodsForIncorrectModifiers(node);
056 checkMethodsForOverwritingFinal(node);
057 checkNoAbstractMethodsNonabstractClass(node);
058 }
059 super.visitClass(node);
060 currentClass = oldClass;
061 }
062
063 private void checkNoAbstractMethodsNonabstractClass(ClassNode node) {
064 if (Modifier.isAbstract(node.getModifiers())) return;
065 List abstractMethods = node.getAbstractMethods();
066 if (abstractMethods == null) return;
067 for (Iterator iter = abstractMethods.iterator(); iter.hasNext();) {
068 MethodNode method = (MethodNode) iter.next();
069 String methodName = method.getTypeDescriptor();
070 addError("Can't have an abstract method in a non-abstract class." +
071 " The " + getDescription(node) + " must be declared abstract or" +
072 " the " + getDescription(method) + " must be implemented.", node);
073 }
074 }
075
076 private void checkClassForIncorrectModifiers(ClassNode node) {
077 checkClassForAbstractAndFinal(node);
078 checkClassForOtherModifiers(node);
079 }
080
081 private void checkClassForAbstractAndFinal(ClassNode node) {
082 if (!Modifier.isAbstract(node.getModifiers())) return;
083 if (!Modifier.isFinal(node.getModifiers())) return;
084 if (node.isInterface()) {
085 addError("The " + getDescription(node) +" must not be final. It is by definition abstract.", node);
086 } else {
087 addError("The " + getDescription(node) + " must not be both final and abstract.", node);
088 }
089 }
090
091 private void checkClassForOtherModifiers(ClassNode node) {
092 // TODO: work out why "synchronised" can't be used here
093 checkClassForModifier(node, Modifier.isTransient(node.getModifiers()), "transient");
094 checkClassForModifier(node, Modifier.isVolatile(node.getModifiers()), "volatile");
095 }
096
097 private void checkClassForModifier(ClassNode node, boolean condition, String modifierName) {
098 if (!condition) return;
099 addError("The " + getDescription(node) + " has an incorrect modifier " + modifierName + ".", node);
100 }
101
102 private String getDescription(ClassNode node) {
103 return (node.isInterface() ? "interface" : "class") + " '" + node.getName() + "'";
104 }
105
106 private String getDescription(MethodNode node) {
107 return "method '" + node.getName() + "'";
108 }
109
110 private String getDescription(FieldNode node) {
111 return "field '" + node.getName() + "'";
112 }
113
114 private void checkAbstractDeclaration(MethodNode methodNode) {
115 if (!Modifier.isAbstract(methodNode.getModifiers())) return;
116 if (Modifier.isAbstract(currentClass.getModifiers())) return;
117 addError("Can't have an abstract method in a non-abstract class." +
118 " The " + getDescription(currentClass) + " must be declared abstract or the method '" +
119 methodNode.getTypeDescriptor() + "' must not be abstract.", methodNode);
120 }
121
122 private void checkClassForOverwritingFinal(ClassNode cn) {
123 ClassNode superCN = cn.getSuperClass();
124 if (superCN == null) return;
125 if (!Modifier.isFinal(superCN.getModifiers())) return;
126 StringBuffer msg = new StringBuffer();
127 msg.append("You are not allowed to overwrite the final ");
128 msg.append(getDescription(superCN));
129 msg.append(".");
130 addError(msg.toString(), cn);
131 }
132
133 private void checkImplementsAndExtends(ClassNode node) {
134 ClassNode cn = node.getSuperClass();
135 if (cn.isInterface() && !node.isInterface()) {
136 addError("You are not allowed to extend the " + getDescription(cn) + ", use implements instead.", node);
137 }
138 ClassNode[] interfaces = node.getInterfaces();
139 for (int i = 0; i < interfaces.length; i++) {
140 cn = interfaces[i];
141 if (!cn.isInterface()) {
142 addError("You are not allowed to implement the " + getDescription(cn) + ", use extends instead.", node);
143 }
144 }
145 }
146
147 private void checkMethodsForIncorrectModifiers(ClassNode cn) {
148 if (!cn.isInterface()) return;
149 List methods = cn.getMethods();
150 for (Iterator cnIter = methods.iterator(); cnIter.hasNext();) {
151 MethodNode method = (MethodNode) cnIter.next();
152 if (Modifier.isFinal(method.getModifiers())) {
153 addError("The " + getDescription(method) + " from " + getDescription(cn) +
154 " must not be final. It is by definition abstract.", method);
155 }
156 if (Modifier.isStatic(method.getModifiers()) && !isConstructor(method)) {
157 addError("The " + getDescription(method) + " from " + getDescription(cn) +
158 " must not be static. Only fields may be static in an interface.", method);
159 }
160 }
161 }
162
163 private boolean isConstructor(MethodNode method) {
164 return method.getName().equals("<clinit>");
165 }
166
167 private void checkMethodsForOverwritingFinal(ClassNode cn) {
168 List methods = cn.getMethods();
169 for (Iterator cnIter = methods.iterator(); cnIter.hasNext();) {
170 MethodNode method = (MethodNode) cnIter.next();
171 Parameter[] params = method.getParameters();
172 for (ClassNode superCN = cn.getSuperClass(); superCN != null; superCN = superCN.getSuperClass()) {
173 List superMethods = superCN.getMethods(method.getName());
174 for (Iterator iter = superMethods.iterator(); iter.hasNext();) {
175 MethodNode superMethod = (MethodNode) iter.next();
176 Parameter[] superParams = superMethod.getParameters();
177 if (!hasEqualParameterTypes(params, superParams)) continue;
178 if (!Modifier.isFinal(superMethod.getModifiers())) return;
179 addInvalidUseOfFinalError(method, params, superCN);
180 return;
181 }
182 }
183 }
184 }
185
186 private void addInvalidUseOfFinalError(MethodNode method, Parameter[] parameters, ClassNode superCN) {
187 StringBuffer msg = new StringBuffer();
188 msg.append("You are not allowed to overwrite the final method ").append(method.getName());
189 msg.append("(");
190 boolean needsComma = false;
191 for (int i = 0; i < parameters.length; i++) {
192 if (needsComma) {
193 msg.append(",");
194 } else {
195 needsComma = true;
196 }
197 msg.append(parameters[i].getType());
198 }
199 msg.append(") from ").append(getDescription(superCN));
200 msg.append(".");
201 addError(msg.toString(), method);
202 }
203
204 private boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) {
205 if (first.length != second.length) return false;
206 for (int i = 0; i < first.length; i++) {
207 String ft = first[i].getType().getName();
208 String st = second[i].getType().getName();
209 if (ft.equals(st)) continue;
210 return false;
211 }
212 return true;
213 }
214
215 protected SourceUnit getSourceUnit() {
216 return source;
217 }
218
219 public void visitConstructorCallExpression(ConstructorCallExpression call) {
220 ClassNode type = call.getType();
221 if (Modifier.isAbstract(type.getModifiers())) {
222 addError("You cannot create an instance from the abstract " + getDescription(type) + ".", call);
223 }
224 super.visitConstructorCallExpression(call);
225 }
226
227 public void visitMethod(MethodNode node) {
228 checkAbstractDeclaration(node);
229 checkRepetitiveMethod(node);
230 checkOverloadingPrivateAndPublic(node);
231 super.visitMethod(node);
232 }
233
234 private void checkOverloadingPrivateAndPublic(MethodNode node) {
235 if (isConstructor(node)) return;
236 List methods = currentClass.getMethods(node.getName());
237 boolean hasPrivate=false;
238 boolean hasPublic=false;
239 for (Iterator iter = methods.iterator(); iter.hasNext();) {
240 MethodNode element = (MethodNode) iter.next();
241 if (element == node) continue;
242 int modifiers = element.getModifiers();
243 if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)){
244 hasPublic=true;
245 } else {
246 hasPrivate=true;
247 }
248 }
249 if (hasPrivate && hasPublic) {
250 addError("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.",node);
251 }
252 }
253
254 private void checkRepetitiveMethod(MethodNode node) {
255 if (isConstructor(node)) return;
256 List methods = currentClass.getMethods(node.getName());
257 for (Iterator iter = methods.iterator(); iter.hasNext();) {
258 MethodNode element = (MethodNode) iter.next();
259 if (element == node) continue;
260 if (!element.getDeclaringClass().equals(node.getDeclaringClass())) continue;
261 Parameter[] p1 = node.getParameters();
262 Parameter[] p2 = element.getParameters();
263 if (p1.length != p2.length) continue;
264 addErrorIfParamsAndReturnTypeEqual(p2, p1, node, element);
265 }
266 }
267
268 private void addErrorIfParamsAndReturnTypeEqual(Parameter[] p2, Parameter[] p1,
269 MethodNode node, MethodNode element) {
270 boolean isEqual = true;
271 for (int i = 0; i < p2.length; i++) {
272 isEqual &= p1[i].getType().equals(p2[i].getType());
273 }
274 isEqual &= node.getReturnType().equals(element.getReturnType());
275 if (isEqual) {
276 addError("Repetitive method name/signature for " + getDescription(node) +
277 " in " + getDescription(currentClass) + ".", node);
278 }
279 }
280
281 public void visitField(FieldNode node) {
282 if (currentClass.getField(node.getName()) != node) {
283 addError("The " + getDescription(node) + " is declared multiple times.", node);
284 }
285 checkInterfaceFieldModifiers(node);
286 super.visitField(node);
287 }
288
289 private void checkInterfaceFieldModifiers(FieldNode node) {
290 if (!currentClass.isInterface()) return;
291 if ((node.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)) == 0) {
292 addError("The " + getDescription(node) + " is not 'public final static' but is defined in the " +
293 getDescription(currentClass) + ".", node);
294 }
295 }
296
297 public void visitBinaryExpression(BinaryExpression expression) {
298 if (expression.getOperation().getType() == Types.LEFT_SQUARE_BRACKET &&
299 expression.getRightExpression() instanceof MapEntryExpression) {
300 addError("You tried to use a map entry for an index operation, this is not allowed. " +
301 "Maybe something should be set in parentheses or a comma is missing?",
302 expression.getRightExpression());
303 }
304 super.visitBinaryExpression(expression);
305 }
306
307 public void visitCatchStatement(CatchStatement cs) {
308 if (!(cs.getExceptionType().isDerivedFrom(ClassHelper.make(Throwable.class)))) {
309 addError("Catch statement parameter type is not a subclass of Throwable.", cs);
310 }
311 super.visitCatchStatement(cs);
312 }
313 }