001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.rules;
004:
005: import net.sourceforge.pmd.AbstractRule;
006: import net.sourceforge.pmd.ast.ASTAllocationExpression;
007: import net.sourceforge.pmd.ast.ASTArguments;
008: import net.sourceforge.pmd.ast.ASTArrayDimsAndInits;
009: import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
010: import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
011: import net.sourceforge.pmd.ast.ASTCompilationUnit;
012: import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
013: import net.sourceforge.pmd.ast.ASTEnumDeclaration;
014:
015: import java.util.ArrayList;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.ListIterator;
019:
020: /**
021: * 1. Note all private constructors.
022: * 2. Note all instantiations from outside of the class by way of the private
023: * constructor.
024: * 3. Flag instantiations.
025: * <p/>
026: * <p/>
027: * Parameter types can not be matched because they can come as exposed members
028: * of classes. In this case we have no way to know what the type is. We can
029: * make a best effort though which can filter some?
030: *
031: * @author CL Gilbert (dnoyeb@users.sourceforge.net)
032: * @author David Konecny (david.konecny@)
033: * @author Romain PELISSE, belaran@gmail.com, patch bug#1807370
034: */
035: public class AccessorClassGeneration extends AbstractRule {
036:
037: private List<ClassData> classDataList = new ArrayList<ClassData>();
038: private int classID = -1;
039: private String packageName;
040:
041: public Object visit(ASTEnumDeclaration node, Object data) {
042: return data; // just skip Enums
043: }
044:
045: public Object visit(ASTCompilationUnit node, Object data) {
046: classDataList.clear();
047: packageName = node.getScope().getEnclosingSourceFileScope()
048: .getPackageName();
049: return super .visit(node, data);
050: }
051:
052: private static class ClassData {
053: private String m_ClassName;
054: private List<ASTConstructorDeclaration> m_PrivateConstructors;
055: private List<AllocData> m_Instantiations;
056: /**
057: * List of outer class names that exist above this class
058: */
059: private List<String> m_ClassQualifyingNames;
060:
061: public ClassData(String className) {
062: m_ClassName = className;
063: m_PrivateConstructors = new ArrayList<ASTConstructorDeclaration>();
064: m_Instantiations = new ArrayList<AllocData>();
065: m_ClassQualifyingNames = new ArrayList<String>();
066: }
067:
068: public void addInstantiation(AllocData ad) {
069: m_Instantiations.add(ad);
070: }
071:
072: public Iterator<AllocData> getInstantiationIterator() {
073: return m_Instantiations.iterator();
074: }
075:
076: public void addConstructor(ASTConstructorDeclaration cd) {
077: m_PrivateConstructors.add(cd);
078: }
079:
080: public Iterator<ASTConstructorDeclaration> getPrivateConstructorIterator() {
081: return m_PrivateConstructors.iterator();
082: }
083:
084: public String getClassName() {
085: return m_ClassName;
086: }
087:
088: public void addClassQualifyingName(String name) {
089: m_ClassQualifyingNames.add(name);
090: }
091:
092: public List<String> getClassQualifyingNamesList() {
093: return m_ClassQualifyingNames;
094: }
095: }
096:
097: private static class AllocData {
098: private String m_Name;
099: private int m_ArgumentCount;
100: private ASTAllocationExpression m_ASTAllocationExpression;
101: private boolean isArray;
102:
103: public AllocData(ASTAllocationExpression node,
104: String aPackageName, List<String> classQualifyingNames) {
105: if (node.jjtGetChild(1) instanceof ASTArguments) {
106: ASTArguments aa = (ASTArguments) node.jjtGetChild(1);
107: m_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(
112: "BUG: Expected a ASTClassOrInterfaceType, got a "
113: + node.jjtGetChild(0).getClass());
114: }
115: ASTClassOrInterfaceType an = (ASTClassOrInterfaceType) node
116: .jjtGetChild(0);
117: m_Name = stripString(aPackageName + ".", an.getImage());
118:
119: //strip off outer class names
120: //try OuterClass, then try OuterClass.InnerClass, then try OuterClass.InnerClass.InnerClass2, etc...
121: String findName = "";
122: for (ListIterator<String> li = classQualifyingNames
123: .listIterator(classQualifyingNames.size()); li
124: .hasPrevious();) {
125: String aName = li.previous();
126: findName = aName + "." + findName;
127: if (m_Name.startsWith(findName)) {
128: //strip off name and exit
129: m_Name = m_Name.substring(findName.length());
130: break;
131: }
132: }
133: } else if (node.jjtGetChild(1) instanceof ASTArrayDimsAndInits) {
134: //this is incomplete because I dont need it.
135: // child 0 could be primitive or object (ASTName or ASTPrimitiveType)
136: isArray = true;
137: }
138: m_ASTAllocationExpression = node;
139: }
140:
141: public String getName() {
142: return m_Name;
143: }
144:
145: public int getArgumentCount() {
146: return m_ArgumentCount;
147: }
148:
149: public ASTAllocationExpression getASTAllocationExpression() {
150: return m_ASTAllocationExpression;
151: }
152:
153: public boolean isArray() {
154: return isArray;
155: }
156: }
157:
158: /**
159: * Outer interface visitation
160: */
161: public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
162: if (node.isInterface()) {
163: if (!(node.jjtGetParent().jjtGetParent() instanceof ASTCompilationUnit)) {
164: // not a top level interface
165: String interfaceName = node.getImage();
166: int formerID = getClassID();
167: setClassID(classDataList.size());
168: ClassData newClassData = new ClassData(interfaceName);
169: //store the names of any outer classes of this class in the classQualifyingName List
170: ClassData formerClassData = classDataList.get(formerID);
171: newClassData.addClassQualifyingName(formerClassData
172: .getClassName());
173: classDataList.add(getClassID(), newClassData);
174: Object o = super .visit(node, data);
175: setClassID(formerID);
176: return o;
177: } else {
178: String interfaceName = node.getImage();
179: classDataList.clear();
180: setClassID(0);
181: classDataList.add(getClassID(), new ClassData(
182: interfaceName));
183: Object o = super .visit(node, data);
184: if (o != null) {
185: processRule(o);
186: } else {
187: processRule(data);
188: }
189: setClassID(-1);
190: return o;
191: }
192: } else if (!(node.jjtGetParent().jjtGetParent() instanceof ASTCompilationUnit)) {
193: // not a top level class
194: String className = node.getImage();
195: int formerID = getClassID();
196: setClassID(classDataList.size());
197: ClassData newClassData = new ClassData(className);
198: // TODO
199: // this is a hack to bail out here
200: // but I'm not sure why this is happening
201: // TODO
202: if (formerID == -1 || formerID >= classDataList.size()) {
203: return null;
204: }
205: //store the names of any outer classes of this class in the classQualifyingName List
206: ClassData formerClassData = classDataList.get(formerID);
207: newClassData.addClassQualifyingName(formerClassData
208: .getClassName());
209: classDataList.add(getClassID(), newClassData);
210: Object o = super .visit(node, data);
211: setClassID(formerID);
212: return o;
213: }
214: // outer classes
215: if (!node.isStatic()) { // See bug# 1807370
216: String className = node.getImage();
217: classDataList.clear();
218: setClassID(0);//first class
219: classDataList.add(getClassID(), new ClassData(className));
220: }
221: Object o = super .visit(node, data);
222: if (o != null && !node.isStatic()) { // See bug# 1807370
223: processRule(o);
224: } else {
225: processRule(data);
226: }
227: setClassID(-1);
228: return o;
229: }
230:
231: /**
232: * Store all target constructors
233: */
234: public Object visit(ASTConstructorDeclaration node, Object data) {
235: if (node.isPrivate()) {
236: getCurrentClassData().addConstructor(node);
237: }
238: return super .visit(node, data);
239: }
240:
241: public Object visit(ASTAllocationExpression node, Object data) {
242: // TODO
243: // this is a hack to bail out here
244: // but I'm not sure why this is happening
245: // TODO
246: if (classID == -1 || getCurrentClassData() == null) {
247: return data;
248: }
249: AllocData ad = new AllocData(node, packageName,
250: getCurrentClassData().getClassQualifyingNamesList());
251: if (!ad.isArray()) {
252: getCurrentClassData().addInstantiation(ad);
253: }
254: return super .visit(node, data);
255: }
256:
257: private void processRule(Object ctx) {
258: //check constructors of outerIterator against allocations of innerIterator
259: for (ClassData outerDataSet : classDataList) {
260: for (Iterator<ASTConstructorDeclaration> constructors = outerDataSet
261: .getPrivateConstructorIterator(); constructors
262: .hasNext();) {
263: ASTConstructorDeclaration cd = constructors.next();
264:
265: for (ClassData innerDataSet : classDataList) {
266: if (outerDataSet == innerDataSet) {
267: continue;
268: }
269: for (Iterator<AllocData> allocations = innerDataSet
270: .getInstantiationIterator(); allocations
271: .hasNext();) {
272: AllocData ad = allocations.next();
273: //if the constructor matches the instantiation
274: //flag the instantiation as a generator of an extra class
275: if (outerDataSet.getClassName().equals(
276: ad.getName())
277: && (cd.getParameterCount() == ad
278: .getArgumentCount())) {
279: addViolation(ctx, ad
280: .getASTAllocationExpression());
281: }
282: }
283: }
284: }
285: }
286: }
287:
288: private ClassData getCurrentClassData() {
289: // TODO
290: // this is a hack to bail out here
291: // but I'm not sure why this is happening
292: // TODO
293: if (classID >= classDataList.size()) {
294: return null;
295: }
296: return classDataList.get(classID);
297: }
298:
299: private void setClassID(int ID) {
300: classID = ID;
301: }
302:
303: private int getClassID() {
304: return classID;
305: }
306:
307: //remove = Fire.
308: //value = someFire.Fighter
309: // 0123456789012345
310: //index = 4
311: //remove.size() = 5
312: //value.substring(0,4) = some
313: //value.substring(4 + remove.size()) = Fighter
314: //return "someFighter"
315: private static String stripString(String remove, String value) {
316: String returnValue;
317: int index = value.indexOf(remove);
318: if (index != -1) { //if the package name can start anywhere but 0 please inform the author because this will break
319: returnValue = value.substring(0, index)
320: + value.substring(index + remove.length());
321: } else {
322: returnValue = value;
323: }
324: return returnValue;
325: }
326:
327: }
|