View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTAllocationExpression;
8   import net.sourceforge.pmd.ast.ASTArguments;
9   import net.sourceforge.pmd.ast.ASTArrayDimsAndInits;
10  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
11  import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
12  import net.sourceforge.pmd.ast.ASTCompilationUnit;
13  import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
14  import net.sourceforge.pmd.ast.ASTEnumDeclaration;
15  
16  import java.util.ArrayList;
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.ListIterator;
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 AccessorClassGeneration extends AbstractRule {
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 m_ClassName;
54          private List<ASTConstructorDeclaration> m_PrivateConstructors;
55          private List<AllocData> m_Instantiations;
56          /**
57           * List of outer class names that exist above this class
58           */
59          private List<String> m_ClassQualifyingNames;
60  
61          public ClassData(String className) {
62              m_ClassName = className;
63              m_PrivateConstructors = new ArrayList<ASTConstructorDeclaration>();
64              m_Instantiations = new ArrayList<AllocData>();
65              m_ClassQualifyingNames = new ArrayList<String>();
66          }
67  
68          public void addInstantiation(AllocData ad) {
69              m_Instantiations.add(ad);
70          }
71  
72          public Iterator<AllocData> getInstantiationIterator() {
73              return m_Instantiations.iterator();
74          }
75  
76          public void addConstructor(ASTConstructorDeclaration cd) {
77              m_PrivateConstructors.add(cd);
78          }
79  
80          public Iterator<ASTConstructorDeclaration> getPrivateConstructorIterator() {
81              return m_PrivateConstructors.iterator();
82          }
83  
84          public String getClassName() {
85              return m_ClassName;
86          }
87  
88          public void addClassQualifyingName(String name) {
89              m_ClassQualifyingNames.add(name);
90          }
91  
92          public List<String> getClassQualifyingNamesList() {
93              return m_ClassQualifyingNames;
94          }
95      }
96  
97      private static class AllocData {
98          private String m_Name;
99          private int m_ArgumentCount;
100         private ASTAllocationExpression m_ASTAllocationExpression;
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                 m_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                 m_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 (m_Name.startsWith(findName)) {
122                         //strip off name and exit
123                         m_Name = m_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             m_ASTAllocationExpression = node;
133         }
134 
135         public String getName() {
136             return m_Name;
137         }
138 
139         public int getArgumentCount() {
140             return m_ArgumentCount;
141         }
142 
143         public ASTAllocationExpression getASTAllocationExpression() {
144             return m_ASTAllocationExpression;
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     private static String stripString(String remove, String value) {
298         String returnValue;
299         int index = value.indexOf(remove);
300         if (index != -1) {	//if the package name can start anywhere but 0 please inform the author because this will break
301             returnValue = value.substring(0, index) + value.substring(index + remove.length());
302         } else {
303             returnValue = value;
304         }
305         return returnValue;
306     }
307 
308 }