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   ******************************************************************************/
11  
12  
13  package org.codehaus.groovy.classgen;
14  
15  import java.lang.reflect.Modifier;
16  import java.util.Iterator;
17  import java.util.List;
18  
19  import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
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.control.SourceUnit;
28  import org.objectweb.asm.Opcodes;
29  import org.codehaus.groovy.syntax.Types;
30  
31  
32  /***
33   * ClassCompletionVerifier
34   * 
35   */
36  public class ClassCompletionVerifier extends ClassCodeVisitorSupport {
37      
38      private ClassNode currentClass;
39      private SourceUnit source;
40      
41      public ClassCompletionVerifier(SourceUnit source) {
42          this.source = source;
43      }
44      
45      public ClassNode getClassNode() {
46          return currentClass;
47      }
48      
49      public void visitClass(ClassNode node) {
50          ClassNode oldClass = currentClass;
51          currentClass = node;
52          
53          checkImplementsAndExtends(node);
54          if (source!=null && !source.getErrorCollector().hasErrors()) {
55              checkClassForOverwritingFinal(node);
56              checkMethodsForOverwritingFinal(node);
57              checkNoAbstractMethodsNonabstractClass(node);
58          }
59          
60          super.visitClass(node);
61          
62          currentClass = oldClass;
63      }
64  
65      private void checkNoAbstractMethodsNonabstractClass(ClassNode node) {
66          if (Modifier.isAbstract(node.getModifiers())) return;
67          List abstractMethods = node.getAbstractMethods();
68          if (abstractMethods==null) return;
69          for (Iterator iter = abstractMethods.iterator(); iter.hasNext();) {
70              MethodNode method = (MethodNode) iter.next();
71              String methodName = method.getTypeDescriptor();
72              addError("Can't have an abstract method in a non abstract class."+
73                       " The class '"+node.getName()+"' must be declared abstract or"+
74                       " the method '"+methodName+"' must be implemented.",node);
75          }
76      }
77  
78      private void checkAbstractDeclaration(MethodNode methodNode) {
79          if (!Modifier.isAbstract(methodNode.getModifiers())) return;
80          if (Modifier.isAbstract(currentClass.getModifiers())) return;
81          addError("Can't have an abstract method in a non abstract class." +
82                   " The class '" + currentClass.getName() +  "' must be declared abstract or the method '" +
83                   methodNode.getTypeDescriptor() + "' must not be abstract.",methodNode);
84      }
85      
86      private void checkClassForOverwritingFinal(ClassNode cn) {
87          ClassNode superCN = cn.getSuperClass();
88          if (superCN==null) return;
89          if (!Modifier.isFinal(superCN.getModifiers())) return;
90          StringBuffer msg = new StringBuffer();
91          msg.append("you are not allowed to overwrite the final class ");
92          msg.append(superCN.getName());
93          msg.append(".");
94          addError(msg.toString(),cn);        
95      }
96      
97      private void checkImplementsAndExtends(ClassNode node) {
98          ClassNode cn = node.getSuperClass();
99          if (cn.isInterface() && !node.isInterface()) addError("you are not allowed to extend the Interface "+cn.getName()+", use implements instead", node);
100         ClassNode[] interfaces = node.getInterfaces();
101         for (int i = 0; i < interfaces.length; i++) {
102             cn = interfaces[i];
103             if (!cn.isInterface()) addError ("you are not allowed to implement the Class "+cn.getName()+", use extends instead", node); 
104         }
105     }
106 
107     private void checkMethodsForOverwritingFinal(ClassNode cn) {
108         List l = cn.getMethods();     
109         for (Iterator cnIter = l.iterator(); cnIter.hasNext();) {
110             MethodNode method =(MethodNode) cnIter.next();
111             Parameter[] parameters = method.getParameters();
112             for (ClassNode superCN = cn.getSuperClass(); superCN!=null; superCN=superCN.getSuperClass()){
113                 List methods = superCN.getMethods(method.getName());
114                 for (Iterator iter = methods.iterator(); iter.hasNext();) {
115                     MethodNode m = (MethodNode) iter.next();
116                     Parameter[] np = m.getParameters();
117                     if (!hasEqualParameterTypes(parameters,np)) continue;
118                     if (!Modifier.isFinal(m.getModifiers())) return;
119                     
120                     StringBuffer msg = new StringBuffer();
121                     msg.append("you are not allowed to overwrite the final method ").append(method.getName());
122                     msg.append("(");
123                     boolean semi = false;
124                     for (int i=0; i<parameters.length;i++) {
125                         if (semi) {
126                             msg.append(",");
127                         } else {
128                             semi = true;
129                         }
130                         msg.append(parameters[i].getType());
131                     }
132                     msg.append(")");
133                     msg.append(" from class ").append(superCN.getName()); 
134                     msg.append(".");
135                     addError(msg.toString(),method);
136                     return;
137                 }
138             }
139         }        
140     }
141     
142     private boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) {
143         if (first.length!=second.length) return false;
144         for (int i=0; i<first.length; i++) {
145             String ft = first[i].getType().getName();
146             String st = second[i].getType().getName();
147             if (ft.equals(st)) continue;
148             return false;
149         }        
150         return true; 
151     }
152 
153     protected SourceUnit getSourceUnit() {
154         return source;
155     }
156     
157     public void visitConstructorCallExpression(ConstructorCallExpression call) {
158         ClassNode type = call.getType();
159         if (Modifier.isAbstract(type.getModifiers())) {
160             addError("cannot create an instance from the abstract class "+type.getName(),call);
161         }
162         super.visitConstructorCallExpression(call);
163     }
164     
165     public void visitMethod(MethodNode node) {
166         checkAbstractDeclaration(node);
167         checkRepetitiveMethod(node);
168         super.visitMethod(node);
169     }
170     
171     private void checkRepetitiveMethod(MethodNode node) {
172         if (node.getName().equals("<clinit>")) return;
173         List methods = currentClass.getMethods(node.getName());
174         for (Iterator iter = methods.iterator(); iter.hasNext();) {
175             MethodNode element = (MethodNode) iter.next();
176             if (element==node) continue;
177             if (!element.getDeclaringClass().equals(node.getDeclaringClass())) continue;
178             Parameter[] p1 = node.getParameters();
179             Parameter[] p2 = element.getParameters();
180             if (p1.length!=p2.length) continue;
181             boolean isEqual=true;
182             for (int i = 0; i < p2.length; i++) {
183                 isEqual &= p1[i].equals(p2[i]);
184             }
185             isEqual &= node.getReturnType().equals(element.getReturnType());
186             if (isEqual) {
187                 addError("Repetitive method name/signature in class "+currentClass.getName(),node);
188             }
189         }        
190     }
191 
192     public void visitField(FieldNode node) {
193     	if (currentClass.getField(node.getName())!=node) {
194     		addError("The field "+node.getName()+" is declared multiple times.", node);
195     	}
196     	checkInterfaceFieldModifiers(node);
197     	super.visitField(node);
198     }
199     
200     private void checkInterfaceFieldModifiers(FieldNode node) {
201     	if (!currentClass.isInterface()) return;
202     	if ((node.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL))==0) {
203     		addError("The field "+node.getName()+" is not 'public final static' but part of the interface "+currentClass.getName()+".", node);
204     	}
205     }
206     
207     public void visitBinaryExpression(BinaryExpression expression) {
208         if (expression.getOperation().getType()==Types.LEFT_SQUARE_BRACKET && 
209             expression.getRightExpression() instanceof MapEntryExpression){
210             addError("You tried to use a map entry for an index operation, this is not "+
211                      "allowed. Maybe something should be set in parentheses or a comma is missing?",
212                      expression.getRightExpression());
213         }
214         super.visitBinaryExpression(expression);
215     }
216 
217 }