View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.java.rule.design;
    5
    6   import java.util.ArrayList;
    7   import java.util.Iterator;
    8   import java.util.List;
    9   import java.util.ListIterator;
    10
    11  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
    12  import net.sourceforge.pmd.lang.java.ast.ASTArguments;
    13  import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits;
    14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
    16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
    17  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
    18  import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
    19  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    20
    21  /**
    22   * 1. Note all private constructors.
    23   * 2. Note all instantiations from outside of the class by way of the private
    24   * constructor.
    25   * 3. Flag instantiations.
    26   * <p/>
    27   * <p/>
    28   * Parameter types can not be matched because they can come as exposed members
    29   * of classes.  In this case we have no way to know what the type is.  We can
    30   * make a best effort though which can filter some?
    31   *
    32   * @author CL Gilbert (dnoyeb@users.sourceforge.net)
    33   * @author David Konecny (david.konecny@)
    34   * @author Romain PELISSE, belaran@gmail.com, patch bug#1807370
    35   */
    36  public class AccessorClassGenerationRule extends AbstractJavaRule {
    37
    38      private List<ClassData> classDataList = new ArrayList<ClassData>();
    39      private int classID = -1;
    40      private String packageName;
    41
    42      public Object visit(ASTEnumDeclaration node, Object data) {
    43          return data;  // just skip Enums
    44      }
    45
    46      public Object visit(ASTCompilationUnit node, Object data) {
    47          classDataList.clear();
    48          packageName = node.getScope().getEnclosingSourceFileScope().getPackageName();
    49          return super.visit(node, data);
    50      }
    51
    52      private static class ClassData {
    53          private String className;
    54          private List<ASTConstructorDeclaration> privateConstructors;
    55          private List<AllocData> instantiations;
    56          /**
    57           * List of outer class names that exist above this class
    58           */
    59          private List<String> classQualifyingNames;
    60
    61          public ClassData(String className) {
    62              this.className = className;
    63              this.privateConstructors = new ArrayList<ASTConstructorDeclaration>();
    64              this.instantiations = new ArrayList<AllocData>();
    65              this.classQualifyingNames = new ArrayList<String>();
    66          }
    67
    68          public void addInstantiation(AllocData ad) {
    69              instantiations.add(ad);
    70          }
    71
    72          public Iterator<AllocData> getInstantiationIterator() {
    73              return instantiations.iterator();
    74          }
    75
    76          public void addConstructor(ASTConstructorDeclaration cd) {
    77              privateConstructors.add(cd);
    78          }
    79
    80          public Iterator<ASTConstructorDeclaration> getPrivateConstructorIterator() {
    81              return privateConstructors.iterator();
    82          }
    83
    84          public String getClassName() {
    85              return className;
    86          }
    87
    88          public void addClassQualifyingName(String name) {
    89              classQualifyingNames.add(name);
    90          }
    91
    92          public List<String> getClassQualifyingNamesList() {
    93              return classQualifyingNames;
    94          }
    95      }
    96
    97      private static class AllocData {
    98          private String name;
    99          private int argumentCount;
    100         private ASTAllocationExpression allocationExpression;
    101         private boolean isArray;
    102
    103         public AllocData(ASTAllocationExpression node, String aPackageName, List<String> classQualifyingNames) {
    104             if (node.jjtGetChild(1) instanceof ASTArguments) {
    105                 ASTArguments aa = (ASTArguments) node.jjtGetChild(1);
    106                 argumentCount = aa.getArgumentCount();
    107                 //Get name and strip off all superfluous data
    108                 //strip off package name if it is current package
    109                 if (!(node.jjtGetChild(0) instanceof ASTClassOrInterfaceType)) {
    110                     throw new RuntimeException("BUG: Expected a ASTClassOrInterfaceType, got a " + node.jjtGetChild(0).getClass());
    111                 }
    112                 ASTClassOrInterfaceType an = (ASTClassOrInterfaceType) node.jjtGetChild(0);
    113                 name = stripString(aPackageName + '.', an.getImage());
    114
    115                 //strip off outer class names
    116                 //try OuterClass, then try OuterClass.InnerClass, then try OuterClass.InnerClass.InnerClass2, etc...
    117                 String findName = "";
    118                 for (ListIterator<String> li = classQualifyingNames.listIterator(classQualifyingNames.size()); li.hasPrevious();) {
    119                     String aName = li.previous();
    120                     findName = aName + '.' + findName;
    121                     if (name.startsWith(findName)) {
    122                         //strip off name and exit
    123                         name = name.substring(findName.length());
    124                         break;
    125                     }
    126                 }
    127             } else if (node.jjtGetChild(1) instanceof ASTArrayDimsAndInits) {
    128                 //this is incomplete because I dont need it.
    129                 //				child 0 could be primitive or object (ASTName or ASTPrimitiveType)
    130                 isArray = true;
    131             }
    132             allocationExpression = node;
    133         }
    134
    135         public String getName() {
    136             return name;
    137         }
    138
    139         public int getArgumentCount() {
    140             return argumentCount;
    141         }
    142
    143         public ASTAllocationExpression getASTAllocationExpression() {
    144             return allocationExpression;
    145         }
    146
    147         public boolean isArray() {
    148             return isArray;
    149         }
    150     }
    151
    152     /**
    153      * Outer interface visitation
    154      */
    155     public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    156         if (node.isInterface()) {
    157             if (!(node.jjtGetParent().jjtGetParent() instanceof ASTCompilationUnit)) {
    158                 // not a top level interface
    159                 String interfaceName = node.getImage();
    160                 int formerID = getClassID();
    161                 setClassID(classDataList.size());
    162                 ClassData newClassData = new ClassData(interfaceName);
    163                 //store the names of any outer classes of this class in the classQualifyingName List
    164                 ClassData formerClassData = classDataList.get(formerID);
    165                 newClassData.addClassQualifyingName(formerClassData.getClassName());
    166                 classDataList.add(getClassID(), newClassData);
    167                 Object o = super.visit(node, data);
    168                 setClassID(formerID);
    169                 return o;
    170             } else {
    171                 String interfaceName = node.getImage();
    172                 classDataList.clear();
    173                 setClassID(0);
    174                 classDataList.add(getClassID(), new ClassData(interfaceName));
    175                 Object o = super.visit(node, data);
    176                 if (o != null) {
    177                     processRule(o);
    178                 } else {
    179                     processRule(data);
    180                 }
    181                 setClassID(-1);
    182                 return o;
    183             }
    184         } else if (!(node.jjtGetParent().jjtGetParent() instanceof ASTCompilationUnit)) {
    185             // not a top level class
    186             String className = node.getImage();
    187             int formerID = getClassID();
    188             setClassID(classDataList.size());
    189             ClassData newClassData = new ClassData(className);
    190             // TODO
    191             // this is a hack to bail out here
    192             // but I'm not sure why this is happening
    193             // TODO
    194             if (formerID == -1 || formerID >= classDataList.size()) {
    195                 return null;
    196             }
    197             //store the names of any outer classes of this class in the classQualifyingName List
    198             ClassData formerClassData = classDataList.get(formerID);
    199             newClassData.addClassQualifyingName(formerClassData.getClassName());
    200             classDataList.add(getClassID(), newClassData);
    201             Object o = super.visit(node, data);
    202             setClassID(formerID);
    203             return o;
    204         }
    205         // outer classes
    206         if ( ! node.isStatic() ) {	// See bug# 1807370
    207         String className = node.getImage();
    208         classDataList.clear();
    209         setClassID(0);//first class
    210         classDataList.add(getClassID(), new ClassData(className));
    211         }
    212         Object o = super.visit(node, data);
    213         if (o != null && ! node.isStatic() ) { // See bug# 1807370
    214             processRule(o);
    215         } else {
    216             processRule(data);
    217         }
    218         setClassID(-1);
    219         return o;
    220     }
    221
    222     /**
    223      * Store all target constructors
    224      */
    225     public Object visit(ASTConstructorDeclaration node, Object data) {
    226         if (node.isPrivate()) {
    227             getCurrentClassData().addConstructor(node);
    228         }
    229         return super.visit(node, data);
    230     }
    231
    232     public Object visit(ASTAllocationExpression node, Object data) {
    233         // TODO
    234         // this is a hack to bail out here
    235         // but I'm not sure why this is happening
    236         // TODO
    237         if (classID == -1 || getCurrentClassData() == null) {
    238             return data;
    239         }
    240         AllocData ad = new AllocData(node, packageName, getCurrentClassData().getClassQualifyingNamesList());
    241         if (!ad.isArray()) {
    242             getCurrentClassData().addInstantiation(ad);
    243         }
    244         return super.visit(node, data);
    245     }
    246
    247     private void processRule(Object ctx) {
    248         //check constructors of outerIterator against allocations of innerIterator
    249         for (ClassData outerDataSet : classDataList) {
    250             for (Iterator<ASTConstructorDeclaration> constructors = outerDataSet.getPrivateConstructorIterator(); constructors.hasNext();) {
    251                 ASTConstructorDeclaration cd = constructors.next();
    252
    253                 for (ClassData innerDataSet : classDataList) {
    254                     if (outerDataSet == innerDataSet) {
    255                         continue;
    256                     }
    257                     for (Iterator<AllocData> allocations = innerDataSet.getInstantiationIterator(); allocations.hasNext();) {
    258                         AllocData ad = allocations.next();
    259                         //if the constructor matches the instantiation
    260                         //flag the instantiation as a generator of an extra class
    261                         if (outerDataSet.getClassName().equals(ad.getName()) && (cd.getParameterCount() == ad.getArgumentCount())) {
    262                             addViolation(ctx, ad.getASTAllocationExpression());
    263                         }
    264                     }
    265                 }
    266             }
    267         }
    268     }
    269
    270     private ClassData getCurrentClassData() {
    271         // TODO
    272         // this is a hack to bail out here
    273         // but I'm not sure why this is happening
    274         // TODO
    275         if (classID >= classDataList.size()) {
    276             return null;
    277         }
    278         return classDataList.get(classID);
    279     }
    280
    281     private void setClassID(int id) {
    282         classID = id;
    283     }
    284
    285     private int getClassID() {
    286         return classID;
    287     }
    288
    289     //remove = Fire.
    290     //value = someFire.Fighter
    291     //        0123456789012345
    292     //index = 4
    293     //remove.size() = 5
    294     //value.substring(0,4) = some
    295     //value.substring(4 + remove.size()) = Fighter
    296     //return "someFighter"
    297
    298     // TODO move this into StringUtil
    299     private static String stripString(String remove, String value) {
    300         String returnValue;
    301         int index = value.indexOf(remove);
    302         if (index != -1) {	//if the package name can start anywhere but 0 please inform the author because this will break
    303             returnValue = value.substring(0, index) + value.substring(index + remove.length());
    304         } else {
    305             returnValue = value;
    306         }
    307         return returnValue;
    308     }
    309
    310 }