1
2
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
27
28
29
30
31
32
33
34
35
36
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;
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
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
110
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
119
120
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
128 name = name.substring(findName.length());
129 break;
130 }
131 }
132 } else if (node.jjtGetChild(1) instanceof ASTArrayDimsAndInits) {
133
134
135
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
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()) {
194 String typeName = node.getImage();
195 classDataList.clear();
196 setClassID(0);
197 classDataList.add(getClassID(), new ClassData(typeName));
198 }
199 Object o = super.visit(node, data);
200 if (o != null && !node.isStatic()) {
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
215
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
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
236
237
238
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
251
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
265
266
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
279
280
281
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
297
298
299
300
301
302
303
304
305
306 private static String stripString(String remove, String value) {
307 String returnValue;
308 int index = value.indexOf(remove);
309 if (index != -1) {
310
311 returnValue = value.substring(0, index) + value.substring(index + remove.length());
312 } else {
313 returnValue = value;
314 }
315 return returnValue;
316 }
317
318 }