View Javadoc

1   /********************************************************************************
2    * Copyright (c) 2004 IBM Corporation and others.
3    * All rights reserved.   This program and the accompanying materials
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    *
8    * Contributors:
9    * IBM - Initial API and implementation
10   * Groovy community - subsequent modifications
11   ******************************************************************************/
12  package org.codehaus.groovy.classgen;
13  
14  import java.lang.reflect.Modifier;
15  import java.util.Iterator;
16  import java.util.List;
17  
18  import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
19  import org.codehaus.groovy.ast.ClassHelper;
20  import org.codehaus.groovy.ast.ClassNode;
21  import org.codehaus.groovy.ast.FieldNode;
22  import org.codehaus.groovy.ast.MethodNode;
23  import org.codehaus.groovy.ast.Parameter;
24  import org.codehaus.groovy.ast.expr.BinaryExpression;
25  import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
26  import org.codehaus.groovy.ast.expr.MapEntryExpression;
27  import org.codehaus.groovy.ast.stmt.CatchStatement;
28  import org.codehaus.groovy.control.SourceUnit;
29  import org.objectweb.asm.Opcodes;
30  import org.codehaus.groovy.syntax.Types;
31  
32  /***
33   * ClassCompletionVerifier
34   */
35  public class ClassCompletionVerifier extends ClassCodeVisitorSupport {
36  
37      private ClassNode currentClass;
38      private SourceUnit source;
39  
40      public ClassCompletionVerifier(SourceUnit source) {
41          this.source = source;
42      }
43  
44      public ClassNode getClassNode() {
45          return currentClass;
46      }
47  
48      public void visitClass(ClassNode node) {
49          ClassNode oldClass = currentClass;
50          currentClass = node;
51          checkImplementsAndExtends(node);
52          if (source != null && !source.getErrorCollector().hasErrors()) {
53              checkClassForIncorrectModifiers(node);
54              checkClassForOverwritingFinal(node);
55              checkMethodsForIncorrectModifiers(node);
56              checkMethodsForOverwritingFinal(node);
57              checkNoAbstractMethodsNonabstractClass(node);
58          }
59          super.visitClass(node);
60          currentClass = oldClass;
61      }
62  
63      private void checkNoAbstractMethodsNonabstractClass(ClassNode node) {
64          if (Modifier.isAbstract(node.getModifiers())) return;
65          List abstractMethods = node.getAbstractMethods();
66          if (abstractMethods == null) return;
67          for (Iterator iter = abstractMethods.iterator(); iter.hasNext();) {
68              MethodNode method = (MethodNode) iter.next();
69              String methodName = method.getTypeDescriptor();
70              addError("Can't have an abstract method in a non-abstract class." +
71                      " The " + getDescription(node) + " must be declared abstract or" +
72                      " the " + getDescription(method) + " must be implemented.", node);
73          }
74      }
75  
76      private void checkClassForIncorrectModifiers(ClassNode node) {
77          checkClassForAbstractAndFinal(node);
78          checkClassForOtherModifiers(node);
79      }
80  
81      private void checkClassForAbstractAndFinal(ClassNode node) {
82          if (!Modifier.isAbstract(node.getModifiers())) return;
83          if (!Modifier.isFinal(node.getModifiers())) return;
84          if (node.isInterface()) {
85              addError("The " + getDescription(node) +" must not be final. It is by definition abstract.", node);
86          } else {
87              addError("The " + getDescription(node) + " must not be both final and abstract.", node);
88          }
89      }
90  
91      private void checkClassForOtherModifiers(ClassNode node) {
92          // TODO: work out why "synchronised" can't be used here
93          checkClassForModifier(node, Modifier.isTransient(node.getModifiers()), "transient");
94          checkClassForModifier(node, Modifier.isVolatile(node.getModifiers()), "volatile");
95      }
96  
97      private void checkClassForModifier(ClassNode node, boolean condition, String modifierName) {
98          if (!condition) return;
99          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 }