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