001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.design;
005
006 import java.util.ArrayList;
007 import java.util.Iterator;
008 import java.util.List;
009 import java.util.ListIterator;
010
011 import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
012 import net.sourceforge.pmd.lang.java.ast.ASTArguments;
013 import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
016 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
017 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
018 import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
019 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
020
021 /**
022 * 1. Note all private constructors.
023 * 2. Note all instantiations from outside of the class by way of the private
024 * constructor.
025 * 3. Flag instantiations.
026 * <p/>
027 * <p/>
028 * Parameter types can not be matched because they can come as exposed members
029 * of classes. In this case we have no way to know what the type is. We can
030 * make a best effort though which can filter some?
031 *
032 * @author CL Gilbert (dnoyeb@users.sourceforge.net)
033 * @author David Konecny (david.konecny@)
034 * @author Romain PELISSE, belaran@gmail.com, patch bug#1807370
035 */
036 public class AccessorClassGenerationRule extends AbstractJavaRule {
037
038 private List<ClassData> classDataList = new ArrayList<ClassData>();
039 private int classID = -1;
040 private String packageName;
041
042 public Object visit(ASTEnumDeclaration node, Object data) {
043 return data; // just skip Enums
044 }
045
046 public Object visit(ASTCompilationUnit node, Object data) {
047 classDataList.clear();
048 packageName = node.getScope().getEnclosingSourceFileScope().getPackageName();
049 return super.visit(node, data);
050 }
051
052 private static class ClassData {
053 private String className;
054 private List<ASTConstructorDeclaration> privateConstructors;
055 private List<AllocData> instantiations;
056 /**
057 * List of outer class names that exist above this class
058 */
059 private List<String> classQualifyingNames;
060
061 public ClassData(String className) {
062 this.className = className;
063 this.privateConstructors = new ArrayList<ASTConstructorDeclaration>();
064 this.instantiations = new ArrayList<AllocData>();
065 this.classQualifyingNames = new ArrayList<String>();
066 }
067
068 public void addInstantiation(AllocData ad) {
069 instantiations.add(ad);
070 }
071
072 public Iterator<AllocData> getInstantiationIterator() {
073 return instantiations.iterator();
074 }
075
076 public void addConstructor(ASTConstructorDeclaration cd) {
077 privateConstructors.add(cd);
078 }
079
080 public Iterator<ASTConstructorDeclaration> getPrivateConstructorIterator() {
081 return privateConstructors.iterator();
082 }
083
084 public String getClassName() {
085 return className;
086 }
087
088 public void addClassQualifyingName(String name) {
089 classQualifyingNames.add(name);
090 }
091
092 public List<String> getClassQualifyingNamesList() {
093 return classQualifyingNames;
094 }
095 }
096
097 private static class AllocData {
098 private String name;
099 private int argumentCount;
100 private ASTAllocationExpression allocationExpression;
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 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 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 (name.startsWith(findName)) {
122 //strip off name and exit
123 name = 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 allocationExpression = node;
133 }
134
135 public String getName() {
136 return name;
137 }
138
139 public int getArgumentCount() {
140 return argumentCount;
141 }
142
143 public ASTAllocationExpression getASTAllocationExpression() {
144 return allocationExpression;
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
298 // TODO move this into StringUtil
299 private static String stripString(String remove, String value) {
300 String returnValue;
301 int index = value.indexOf(remove);
302 if (index != -1) { //if the package name can start anywhere but 0 please inform the author because this will break
303 returnValue = value.substring(0, index) + value.substring(index + remove.length());
304 } else {
305 returnValue = value;
306 }
307 return returnValue;
308 }
309
310 }
|