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 }