001: /*
002: * FindBugs - Find Bugs in Java programs
003: * Copyright (C) 2005, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import java.io.IOException;
023: import java.nio.ByteBuffer;
024: import java.nio.CharBuffer;
025: import java.nio.charset.CharacterCodingException;
026: import java.nio.charset.Charset;
027: import java.nio.charset.CharsetEncoder;
028: import java.security.MessageDigest;
029: import java.security.NoSuchAlgorithmException;
030: import java.util.Arrays;
031: import java.util.Comparator;
032: import java.util.HashMap;
033: import java.util.Map;
034:
035: import org.apache.bcel.classfile.Field;
036: import org.apache.bcel.classfile.JavaClass;
037: import org.apache.bcel.classfile.Method;
038:
039: import edu.umd.cs.findbugs.xml.XMLOutput;
040: import edu.umd.cs.findbugs.xml.XMLWriteable;
041:
042: /**
043: * Compute a hash of method names and signatures.
044: * This allows us to find out when a class has been renamed, but
045: * not changed in any other obvious way.
046: *
047: * @author David Hovemeyer
048: */
049: public class ClassHash implements XMLWriteable, Comparable<ClassHash> {
050: /**
051: * XML element name for a ClassHash.
052: */
053: public static final String CLASS_HASH_ELEMENT_NAME = "ClassHash";
054:
055: /**
056: * XML element name for a MethodHash.
057: */
058: public static final String METHOD_HASH_ELEMENT_NAME = "MethodHash";
059:
060: // Fields
061: private String className;
062: private byte[] classHash;
063: private Map<XMethod, MethodHash> methodHashMap;
064:
065: /**
066: * Constructor.
067: */
068: public ClassHash() {
069: this .methodHashMap = new HashMap<XMethod, MethodHash>();
070: }
071:
072: /**
073: * Constructor.
074: *
075: * @param classHash pre-computed class hash
076: */
077: public ClassHash(String className, byte[] classHash) {
078: this ();
079: this .className = className;
080: this .classHash = new byte[classHash.length];
081: System.arraycopy(classHash, 0, this .classHash, 0,
082: classHash.length);
083: }
084:
085: /**
086: * Set method hash for given method.
087: *
088: * @param method the method
089: * @param methodHash the method hash
090: */
091: public void setMethodHash(XMethod method, byte[] methodHash) {
092: methodHashMap.put(method, new MethodHash(method.getName(),
093: method.getSignature(), method.isStatic(), methodHash));
094: }
095:
096: /**
097: * @return Returns the className.
098: */
099: public String getClassName() {
100: return className;
101: }
102:
103: /**
104: * Get class hash.
105: *
106: * @return the class hash
107: */
108: public byte[] getClassHash() {
109: return classHash;
110: }
111:
112: /**
113: * Set class hash.
114: *
115: * @param classHash the class hash value to set
116: */
117: public void setClassHash(byte[] classHash) {
118: this .classHash = new byte[classHash.length];
119: System.arraycopy(classHash, 0, this .classHash, 0,
120: classHash.length);
121: }
122:
123: /**
124: * Get method hash for given method.
125: *
126: * @param method the method
127: * @return the MethodHash
128: */
129: public MethodHash getMethodHash(XMethod method) {
130: return methodHashMap.get(method);
131: }
132:
133: /**
134: * Compute hash for given class and all of its methods.
135: *
136: * @param javaClass the class
137: * @return this object
138: */
139: public ClassHash computeHash(JavaClass javaClass) {
140: this .className = javaClass.getClassName();
141:
142: Method[] methodList = new Method[javaClass.getMethods().length];
143:
144: // Sort methods
145: System.arraycopy(javaClass.getMethods(), 0, methodList, 0,
146: javaClass.getMethods().length);
147: Arrays.sort(methodList, new Comparator<Method>() {
148: public int compare(Method o1, Method o2) {
149: // sort by name, then signature
150: int cmp = o1.getName().compareTo(o2.getName());
151: if (cmp != 0)
152: return cmp;
153: return o1.getSignature().compareTo(o2.getSignature());
154:
155: }
156: });
157:
158: Field[] fieldList = new Field[javaClass.getFields().length];
159:
160: // Sort fields
161: System.arraycopy(javaClass.getFields(), 0, fieldList, 0,
162: javaClass.getFields().length);
163: Arrays.sort(fieldList, new Comparator<Field>() {
164: /* (non-Javadoc)
165: * @see java.util.Comparator#compare(T, T)
166: */
167: public int compare(Field o1, Field o2) {
168: int cmp = o1.getName().compareTo(o2.getName());
169: if (cmp != 0)
170: return cmp;
171: return o1.getSignature().compareTo(o2.getSignature());
172: }
173: });
174:
175: MessageDigest digest;
176: try {
177: digest = MessageDigest.getInstance("MD5");
178: } catch (NoSuchAlgorithmException e) {
179: throw new IllegalStateException(
180: "No algorithm for computing class hash", e);
181: }
182:
183: // Compute digest of method names and signatures, in order.
184: // Also, compute method hashes.
185: CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
186: for (Method method : methodList) {
187: work(digest, method.getName(), encoder);
188: work(digest, method.getSignature(), encoder);
189:
190: MethodHash methodHash = new MethodHash()
191: .computeHash(method);
192: methodHashMap.put(
193: XFactory.createXMethod(javaClass, method),
194: methodHash);
195: }
196:
197: // Compute digest of field names and signatures.
198: for (Field field : fieldList) {
199: work(digest, field.getName(), encoder);
200: work(digest, field.getSignature(), encoder);
201: }
202:
203: classHash = digest.digest();
204:
205: return this ;
206: }
207:
208: private static void work(MessageDigest digest, String s,
209: CharsetEncoder encoder) {
210: try {
211: CharBuffer cbuf = CharBuffer.allocate(s.length());
212: cbuf.put(s);
213: cbuf.flip();
214:
215: ByteBuffer buf = encoder.encode(cbuf);
216: // System.out.println("pos="+buf.position() +",limit=" + buf.limit());
217: int nbytes = buf.limit();
218: byte[] encodedBytes = new byte[nbytes];
219: buf.get(encodedBytes);
220:
221: digest.update(encodedBytes);
222: } catch (CharacterCodingException e) {
223: // This should never happen, since we're encoding to UTF-8.
224: }
225: }
226:
227: public void writeXML(XMLOutput xmlOutput) throws IOException {
228: xmlOutput.startTag(CLASS_HASH_ELEMENT_NAME);
229: xmlOutput.addAttribute("class", className);
230: xmlOutput.addAttribute("value", hashToString(classHash));
231: xmlOutput.stopTag(false);
232:
233: for (Map.Entry<XMethod, MethodHash> entry : methodHashMap
234: .entrySet()) {
235: xmlOutput.startTag(METHOD_HASH_ELEMENT_NAME);
236: xmlOutput.addAttribute("name", entry.getKey().getName());
237: xmlOutput.addAttribute("signature", entry.getKey()
238: .getSignature());
239: xmlOutput.addAttribute("isStatic", String.valueOf(entry
240: .getKey().isStatic()));
241: xmlOutput.addAttribute("value", hashToString(entry
242: .getValue().getMethodHash()));
243: xmlOutput.stopTag(true);
244: }
245:
246: xmlOutput.closeTag(CLASS_HASH_ELEMENT_NAME);
247: }
248:
249: private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4',
250: '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', };
251:
252: /**
253: * Convert a hash to a string of hex digits.
254: *
255: * @param hash the hash
256: * @return a String representation of the hash
257: */
258: public static String hashToString(byte[] hash) {
259: StringBuffer buf = new StringBuffer();
260: for (byte b : hash) {
261: buf.append(HEX_CHARS[(b >> 4) & 0xF]);
262: buf.append(HEX_CHARS[b & 0xF]);
263: }
264: return buf.toString();
265: }
266:
267: private static int hexDigitValue(char c) {
268: if (c >= '0' && c <= '9')
269: return c - '0';
270: else if (c >= 'a' && c <= 'f')
271: return 10 + (c - 'a');
272: else if (c >= 'A' && c <= 'F')
273: return 10 + (c - 'A');
274: else
275: throw new IllegalArgumentException(
276: "Illegal hex character: " + c);
277: }
278:
279: /**
280: * Convert a string of hex digits to a hash.
281: *
282: * @param s string of hex digits
283: * @return the hash value represented by the string
284: */
285: public static byte[] stringToHash(String s) {
286: if (s.length() % 2 != 0)
287: throw new IllegalArgumentException("Invalid hash string: "
288: + s);
289: byte[] hash = new byte[s.length() / 2];
290: for (int i = 0; i < s.length(); i += 2) {
291: byte b = (byte) ((hexDigitValue(s.charAt(i)) << 4) + hexDigitValue(s
292: .charAt(i + 1)));
293: hash[i / 2] = b;
294: }
295: return hash;
296: }
297:
298: /**
299: * Return whether or not this class hash has the same hash value
300: * as the one given.
301: *
302: * @param other another ClassHash
303: * @return true if the hash values are the same, false if not
304: */
305: public boolean isSameHash(ClassHash other) {
306: return Arrays.equals(classHash, other.classHash);
307: }
308:
309: @Override
310: public int hashCode() {
311: if (classHash == null)
312: return 0;
313:
314: int result = 1;
315: for (byte element : classHash)
316: result = 31 * result + element;
317:
318: return result;
319:
320: }
321:
322: @Override
323: public boolean equals(Object o) {
324: if (!(o instanceof ClassHash))
325: return false;
326: return isSameHash((ClassHash) o);
327: }
328:
329: /* (non-Javadoc)
330: * @see java.lang.Comparable#compareTo(T)
331: */
332: public int compareTo(ClassHash other) {
333: int cmp = MethodHash.compareHashes(this .classHash,
334: other.classHash);
335: //System.out.println(this + " <=> " + other + ": compareTo=" + cmp);
336: return cmp;
337: }
338:
339: /* (non-Javadoc)
340: * @see java.lang.Object#toString()
341: */
342:
343: @Override
344: public String toString() {
345: return getClassName() + ":" + hashToString(this.classHash);
346: }
347: }
|