KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > model > ClassFeatureSet


1 /*
2  * FindBugs - Find Bugs in Java programs
3  * Copyright (C) 2005, University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.model;
21
22 import java.io.IOException JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Set JavaDoc;
26
27 import org.apache.bcel.Repository;
28 import org.apache.bcel.classfile.Code;
29 import org.apache.bcel.classfile.Field;
30 import org.apache.bcel.classfile.FieldOrMethod;
31 import org.apache.bcel.classfile.JavaClass;
32 import org.apache.bcel.classfile.Method;
33
34 import edu.umd.cs.findbugs.ba.Hierarchy;
35 import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
36 import edu.umd.cs.findbugs.ba.SignatureParser;
37 import edu.umd.cs.findbugs.xml.XMLAttributeList;
38 import edu.umd.cs.findbugs.xml.XMLOutput;
39 import edu.umd.cs.findbugs.xml.XMLWriteable;
40
41 /**
42  * Features of a class which may be used to identify it if it is renamed
43  * or modified.
44  *
45  * @author David Hovemeyer
46  */

47 public class ClassFeatureSet implements XMLWriteable {
48     public static final String JavaDoc CLASS_NAME_KEY = "Class:";
49     public static final String JavaDoc METHOD_NAME_KEY = "Method:";
50     public static final String JavaDoc CODE_LENGTH_KEY = "CodeLength:";
51     public static final String JavaDoc FIELD_NAME_KEY = "Field:";
52     
53     private String JavaDoc className;
54     private boolean isInterface;
55     private Set JavaDoc<String JavaDoc> featureSet;
56     
57     /**
58      * Constructor.
59      * Creates an empty feature set.
60      */

61     public ClassFeatureSet() {
62         this.featureSet = new HashSet JavaDoc<String JavaDoc>();
63     }
64
65     /**
66      * Minimum code length required to add a CodeLength feature.
67      */

68     public static final int MIN_CODE_LENGTH = 10;
69     
70     /**
71      * Initialize from given JavaClass.
72      *
73      * @param javaClass the JavaClass
74      * @return this object
75      */

76     public ClassFeatureSet initialize(JavaClass javaClass) {
77         this.className = javaClass.getClassName();
78         this.isInterface = javaClass.isInterface();
79         
80         addFeature(CLASS_NAME_KEY + transformClassName(javaClass.getClassName()));
81         
82         for (Method method : javaClass.getMethods()) {
83             if (!isSynthetic(method)) {
84                 String JavaDoc transformedMethodSignature = transformMethodSignature(method.getSignature());
85                 
86                 if (method.isStatic() || !overridesSuperclassMethod(javaClass, method)) {
87                     addFeature(METHOD_NAME_KEY + method.getName() + ":" + transformedMethodSignature);
88                 }
89                 
90                 Code code = method.getCode();
91                 if (code != null && code.getCode() != null && code.getCode().length >= MIN_CODE_LENGTH) {
92                     addFeature(CODE_LENGTH_KEY + method.getName() + ":" + transformedMethodSignature +
93                             ":" + code.getCode().length);
94                 }
95             }
96         }
97         
98         for (Field field : javaClass.getFields()) {
99             if (!isSynthetic(field)) {
100                 addFeature(
101                         FIELD_NAME_KEY + field.getName() + ":" +
102                         transformSignature(field.getSignature()));
103             }
104         }
105         
106         return this;
107     }
108     
109     /**
110      * Determine if given method overrides a superclass or superinterface method.
111      *
112      * @param javaClass class defining the method
113      * @param method the method
114      * @return true if the method overrides a superclass/superinterface method, false if not
115      * @throws ClassNotFoundException
116      */

117     private boolean overridesSuperclassMethod(JavaClass javaClass, Method method) {
118         if (method.isStatic())
119             return false;
120         
121         try {
122             JavaClass[] superclassList = javaClass.getSuperClasses();
123             if (superclassList != null) {
124                 JavaClassAndMethod match =
125                     Hierarchy.findMethod(superclassList, method.getName(), method.getSignature(), Hierarchy.INSTANCE_METHOD);
126                 if (match != null)
127                     return true;
128             }
129             
130             JavaClass[] interfaceList = javaClass.getAllInterfaces();
131             if (interfaceList != null) {
132                 JavaClassAndMethod match =
133                     Hierarchy.findMethod(interfaceList, method.getName(), method.getSignature(), Hierarchy.INSTANCE_METHOD);
134                 if (match != null)
135                     return true;
136             }
137             
138             return false;
139         } catch (ClassNotFoundException JavaDoc e) {
140             return true;
141         }
142     }
143
144     /**
145      * Figure out if a class member (field or method) is synthetic.
146      *
147      * @param member a field or method
148      * @return true if the member is synthetic
149      */

150     private boolean isSynthetic(FieldOrMethod member) {
151         if (member.isSynthetic()) // this never works, but worth a try
152
return true;
153         
154         String JavaDoc name = member.getName();
155         
156         if (name.startsWith("class$"))
157             return true;
158         
159         if (name.startsWith("access$"))
160             return true;
161         
162         return false;
163     }
164
165     /**
166      * @return Returns the className.
167      */

168     public String JavaDoc getClassName() {
169         return className;
170     }
171     
172     /**
173      * @param className The className to set.
174      */

175     public void setClassName(String JavaDoc className) {
176         this.className = className;
177     }
178     
179     /**
180      * @return Returns the isInterface.
181      */

182     public boolean isInterface() {
183         return isInterface;
184     }
185     
186     /**
187      * @param isInterface The isInterface to set.
188      */

189     public void setInterface(boolean isInterface) {
190         this.isInterface = isInterface;
191     }
192     
193     public int getNumFeatures() {
194         return featureSet.size();
195     }
196     
197     public void addFeature(String JavaDoc feature) {
198         featureSet.add(feature);
199     }
200     
201     public Iterator JavaDoc<String JavaDoc> featureIterator() {
202         return featureSet.iterator();
203     }
204     
205     public boolean hasFeature(String JavaDoc feature) {
206         return featureSet.contains(feature);
207     }
208     
209     /**
210      * Transform a class name by stripping its package name.
211      *
212      * @param className a class name
213      * @return the transformed class name
214      */

215     public static String JavaDoc transformClassName(String JavaDoc className) {
216         int lastDot = className.lastIndexOf('.');
217         if (lastDot >= 0) {
218             String JavaDoc pkg = className.substring(0, lastDot);
219             if (!isUnlikelyToBeRenamed(pkg)) {
220                 className = className.substring(lastDot + 1);
221             }
222         }
223         return className;
224     }
225     
226     /**
227      * Return true if classes in the given package is unlikely to be renamed:
228      * e.g., because they are part of a public API.
229      *
230      * @param pkg the package name
231      * @return true if classes in the package is unlikely to be renamed
232      */

233     public static boolean isUnlikelyToBeRenamed(String JavaDoc pkg) {
234         return pkg.startsWith("java.");
235     }
236
237     /**
238      * Transform a method signature to allow it to be compared even if
239      * any of its parameter types are moved to another package.
240      *
241      * @param signature a method signature
242      * @return the transformed signature
243      */

244     public static String JavaDoc transformMethodSignature(String JavaDoc signature) {
245         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
246
247         buf.append('(');
248
249         SignatureParser parser = new SignatureParser(signature);
250         for (Iterator JavaDoc<String JavaDoc> i = parser.parameterSignatureIterator(); i.hasNext(); ) {
251             String JavaDoc param = i.next();
252                 param = transformSignature(param);
253             buf.append(param);
254         }
255         
256         buf.append(')');
257         
258         return buf.toString();
259     }
260
261     /**
262      * Transform a field or method parameter signature to allow it to be
263      * compared even if it is moved to another package.
264      *
265      * @param signature the signature
266      * @return the transformed signature
267      */

268     public static String JavaDoc transformSignature(String JavaDoc signature) {
269         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
270         
271         int lastBracket = signature.lastIndexOf('[');
272         if (lastBracket > 0) {
273             buf.append(signature.substring(0, lastBracket+1));
274             signature = signature.substring(lastBracket+1);
275         }
276
277         if (signature.startsWith("L")) {
278             signature = signature.substring(1, signature.length() - 1).replace('/', '.');
279             signature = transformClassName(signature);
280             signature = "L" + signature.replace('.', '/') + ";";
281         }
282         buf.append(signature);
283         
284         return buf.toString();
285     }
286     
287     /**
288      * Minimum number of features which must be present in order
289      * to declare two classes similar.
290      */

291     public static final int MIN_FEATURES = 5;
292     
293     /**
294      * Minimum similarity required to declare two classes similar.
295      */

296     public static final double MIN_MATCH = 0.60;
297     
298     /**
299      * Similarity of classes which don't have enough features to match
300      * exactly, but whose class names match exactly.
301      */

302     public static final double EXACT_CLASS_NAME_MATCH = MIN_MATCH + 0.1;
303     
304     public static double similarity(ClassFeatureSet a, ClassFeatureSet b) {
305         // Some features must match exactly
306
if (a.isInterface() != b.isInterface())
307             return 0.0;
308         
309         if (a.getNumFeatures() < MIN_FEATURES || b.getNumFeatures() < MIN_FEATURES)
310             return a.getClassName().equals(b.getClassName()) ? EXACT_CLASS_NAME_MATCH : 0.0;
311         
312         int numMatch = 0;
313         int max = Math.max(a.getNumFeatures(), b.getNumFeatures());
314         
315         for (Iterator JavaDoc<String JavaDoc> i = a.featureIterator(); i.hasNext();) {
316             String JavaDoc feature = i.next();
317             if (b.hasFeature(feature)) {
318                 ++numMatch;
319             }
320         }
321         
322         return ((double) numMatch / (double) max);
323         
324     }
325     
326     public boolean similarTo(ClassFeatureSet other) {
327         return similarity(this, other) >= MIN_MATCH;
328     }
329     
330     public static void main(String JavaDoc[] args) throws Exception JavaDoc {
331         if (args.length != 2) {
332             System.err.println("Usage: " + ClassFeatureSet.class.getName() + " <class 1> <class 2>");
333             System.exit(1);
334         }
335         
336         JavaClass a = Repository.lookupClass(args[0]);
337         JavaClass b = Repository.lookupClass(args[1]);
338         
339         ClassFeatureSet aFeatures = new ClassFeatureSet().initialize(a);
340         ClassFeatureSet bFeatures = new ClassFeatureSet().initialize(b);
341         
342         System.out.println("Similarity is " + similarity(aFeatures, bFeatures));
343         System.out.println("Classes are" + (aFeatures.similarTo(bFeatures) ? "" : " not") + " similar");
344     }
345     
346     public static final String JavaDoc ELEMENT_NAME = "ClassFeatureSet";
347     public static final String JavaDoc FEATURE_ELEMENT_NAME = "Feature";
348
349     /* (non-Javadoc)
350      * @see edu.umd.cs.findbugs.xml.XMLWriteable#writeXML(edu.umd.cs.findbugs.xml.XMLOutput)
351      */

352     public void writeXML(XMLOutput xmlOutput) throws IOException JavaDoc {
353         xmlOutput.openTag(ELEMENT_NAME, new XMLAttributeList().addAttribute("class", className));
354         for (Iterator JavaDoc<String JavaDoc> i = featureIterator(); i.hasNext(); ) {
355             String JavaDoc feature = i.next();
356             xmlOutput.openCloseTag(
357                     FEATURE_ELEMENT_NAME,
358                     new XMLAttributeList().addAttribute("value", feature));
359         }
360         xmlOutput.closeTag(ELEMENT_NAME);
361     }
362 }
363
Popular Tags