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 }