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   */
35  public class AccessorClassGeneration extends AbstractRule {
36  
37      private List classDataList = new ArrayList();
38      private int classID = -1;
39      privateong> String packageName;
40  
41      public Object visit(ASTEnumDeclaration node, Object data) {
42          return data;  // just skip Enums
43      }
44  
45      public Object visit(ASTCompilationUnit node, Object data) {
46          classDataList.clear();
47          packageName = node.getScope().getEnclosingSourceFileScope().getPackageName();
48          return super.visit(node, data);
49      }
50  
51      private static class ClassData {
52          private String m_ClassName;
53          private List m_PrivateConstructors;
54          private List m_Instantiations;
55          /***
56           * List of outer class names that exist above this class
57           */
58          private List m_ClassQualifyingNames;
59  
60          public ClassData(String className) {
61              m_ClassName = className;
62              m_PrivateConstructors = new ArrayList();
63              m_Instantiations = new ArrayList();
64              m_ClassQualifyingNames = new ArrayList();
65          }
66  
67          public void addInstantiation(AllocData ad) {
68              m_Instantiations.add(ad);
69          }
70  
71          public Iterator getInstantiationIterator() {
72              return m_Instantiations.iterator();
73          }
74  
75          public void addConstructor(ASTConstructorDeclaration cd) {
76              m_PrivateConstructors.add(cd);
77          }
78  
79          public Iterator getPrivateConstructorIterator() {
80              return m_PrivateConstructors.iterator();
81          }
82  
83          public String getClassName() {
84              return m_ClassName;
85          }
86  
87          public void addClassQualifyingName(String name) {
88              m_ClassQualifyingNames.add(name);
89          }
90  
91          public List getClassQualifyingNamesList() {
92              return m_ClassQualifyingNames;
93          }
94      }
95  
96      private static class AllocData {
97          private String m_Name;
98          private int m_ArgumentCount;
99          private ASTAllocationExpression m_ASTAllocationExpression;
100         private boolean isArray;
101 
102         public AllocData(ASTAllocationExpression node, String aPackageName, List classQualifyingNames) {
103             if (node.jjtGetChild(1) instanceof ASTArguments) {
104                 ASTArguments aa = (ASTArguments) node.jjtGetChild(1);
105                 m_ArgumentCount = aa.getArgumentCount();
106                 //Get name and strip off all superfluous data
107                 //strip off package name if it is current package
108                 if (!(node.jjtGetChild(0) instanceof ASTClassOrInterfaceType)) {
109                     throw new RuntimeException("BUG: Expected a ASTClassOrInterfaceType, got a " + node.jjtGetChild(0).getClass());
110                 }
111                 ASTClassOrInterfaceType an = (ASTClassOrInterfaceType) node.jjtGetChild(0);
112                 m_Name = stripString(aPackageName + ".", an.getImage());
113 
114                 //strip off outer class names
115                 //try OuterClass, then try OuterClass.InnerClass, then try OuterClass.InnerClass.InnerClass2, etc...
116                 String findName = "";
117                 for (ListIterator li = classQualifyingNames.listIterator(classQualifyingNames.size()); li.hasPrevious();) {
118                     String aName = (String) li.previous();
119                     findName = aName + "." + findName;
120                     if (m_Name.startsWith(findName)) {
121                         //strip off name and exit
122                         m_Name = m_Name.substring(findName.length());
123                         break;
124                     }
125                 }
126             } else if (node.jjtGetChild(1) instanceof ASTArrayDimsAndInits) {
127                 //this is incomplete because I dont need it.
128                 //				child 0 could be primitive or object (ASTName or ASTPrimitiveType)
129                 isArray = true;
130             }
131             m_ASTAllocationExpression = node;
132         }
133 
134         public String getName() {
135             return m_Name;
136         }
137 
138         public int getArgumentCount() {
139             return m_ArgumentCount;
140         }
141 
142         public ASTAllocationExpression getASTAllocationExpression() {
143             return m_ASTAllocationExpression;
144         }
145 
146         public boolean isArray() {
147             return isArray;
148         }
149     }
150 
151     /***
152      * Outer interface visitation
153      */
154     public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
155         if (node.isInterface()) {
156             if (node.isNested()) {
157                 String interfaceName = node.getImage();
158                 int formerID = getClassID();
159                 setClassID(classDataList.size());
160                 ClassData newClassData = new ClassData(interfaceName);
161                 //store the names of any outer classes of this class in the classQualifyingName List
162                 ClassData formerClassData = (ClassData) classDataList.get(formerID);
163                 newClassData.addClassQualifyingName(formerClassData.getClassName());
164                 classDataList.add(getClassID(), newClassData);
165                 Object o = super.visit(node, data);
166                 setClassID(formerID);
167                 return o;
168             } else {
169                 String interfaceName = node.getImage();
170                 classDataList.clear();
171                 setClassID(0);
172                 classDataList.add(getClassID(), new ClassData(interfaceName));
173                 Object o = super.visit(node, data);
174                 if (o != null) {
175                     processRule(o);
176                 } else {
177                     processRule(data);
178                 }
179                 setClassID(-1);
180                 return o;
181             }
182         } else if (node.isNested()) {
183             String className = node.getImage();
184             int formerID = getClassID();
185             setClassID(classDataList.size());
186             ClassData newClassData = new ClassData(className);
187             // TODO
188             // this is a hack to bail out here
189             // but I'm not sure why this is happening
190             // TODO
191             if (formerID == -1 || formerID >= classDataList.size()) {
192                 return null;
193             }
194             //store the names of any outer classes of this class in the classQualifyingName List
195             ClassData formerClassData = (ClassData) classDataList.get(formerID);
196             newClassData.addClassQualifyingName(formerClassData.getClassName());
197             classDataList.add(getClassID(), newClassData);
198             Object o = super.visit(node, data);
199             setClassID(formerID);
200             return o;
201         }
202         // outer classes
203         String className = node.getImage();
204         classDataList.clear();
205         setClassID(0);//first class
206         classDataList.add(getClassID(), new ClassData(className));
207         Object o = super.visit(node, data);
208         if (o != null) {
209             processRule(o);
210         } else {
211             processRule(data);
212         }
213         setClassID(-1);
214         return o;
215     }
216 
217     /***
218      * Store all target constructors
219      */
220     public Object visit(ASTConstructorDeclaration node, Object data) {
221         if (node.isPrivate()) {
222             getCurrentClassData().addConstructor(node);
223         }
224         return super.visit(node, data);
225     }
226 
227     public Object visit(ASTAllocationExpression node, Object data) {
228         // TODO
229         // this is a hack to bail out here
230         // but I'm not sure why this is happening
231         // TODO
232         if (classID == -1 || getCurrentClassData() == null) {
233             return data;
234         }
235         AllocData ad = new AllocData(node, packageName, getCurrentClassData().getClassQualifyingNamesList());
236         if (ad.isArray() == false) {
237             getCurrentClassData().addInstantiation(ad);
238         }
239         return super.visit(node, data);
240     }
241 
242     private void processRule(Object ctx) {
243         //check constructors of outerIterator against allocations of innerIterator
244         for (Iterator outerIterator = classDataList.iterator(); outerIterator.hasNext();) {
245             ClassData outerDataSet = (ClassData) outerIterator.next();
246             for (Iterator constructors = outerDataSet.getPrivateConstructorIterator(); constructors.hasNext();) {
247                 ASTConstructorDeclaration cd = (ASTConstructorDeclaration) constructors.next();
248 
249                 for (Iterator innerIterator = classDataList.iterator(); innerIterator.hasNext();) {
250                     ClassData innerDataSet = (ClassData) innerIterator.next();
251                     if (outerDataSet == innerDataSet) {
252                         continue;
253                     }
254                     for (Iterator allocations = innerDataSet.getInstantiationIterator(); allocations.hasNext();) {
255                         AllocData ad = (AllocData) allocations.next();
256                         //if the constructor matches the instantiation
257                         //flag the instantiation as a generator of an extra class
258                         if (outerDataSet.getClassName().equals(ad.getName()) && (cd.getParameterCount() == ad.getArgumentCount())) {
259                             addViolation(ctx, ad.getASTAllocationExpression());
260                         }
261                     }
262                 }
263             }
264         }
265     }
266 
267     private ClassData getCurrentClassData() {
268         // TODO
269         // this is a hack to bail out here
270         // but I'm not sure why this is happening
271         // TODO
272         if (classID >= classDataList.size()) {
273             return null;
274         }
275         return (ClassData) classDataList.get(classID);
276     }
277 
278     private void setClassID(int ID) {
279         classID = ID;
280     }
281 
282     private int getClassID() {
283         return classID;
284     }
285 
286     //remove = Fire.
287     //value = someFire.Fighter
288     //        0123456789012345
289     //index = 4
290     //remove.size() = 5
291     //value.substring(0,4) = some
292     //value.substring(4 + remove.size()) = Fighter
293     //return "someFighter"
294     private static String stripString(String remove, String value) {
295         String returnValue;
296         int index = value.indexOf(remove);
297         if (index != -1) {	//if the package name can start anywhere but 0 please inform the author because this will break
298             returnValue = value.substring(0, index) + value.substring(index + remove.length());
299         } else {
300             returnValue = value;
301         }
302         return returnValue;
303     }
304 
305 }