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