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 }