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    }