001: /***
002: * ASM: a very small and fast Java bytecode manipulation framework
003: * Copyright (c) 2000-2005 INRIA, France Telecom
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: * 2. Redistributions in binary form must reproduce the above copyright
012: * notice, this list of conditions and the following disclaimer in the
013: * documentation and/or other materials provided with the distribution.
014: * 3. Neither the name of the copyright holders nor the names of its
015: * contributors may be used to endorse or promote products derived from
016: * this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
028: * THE POSSIBILITY OF SUCH DAMAGE.
029: */package com.tc.asm.commons;
030:
031: import java.io.ByteArrayOutputStream;
032: import java.io.DataOutputStream;
033: import java.io.IOException;
034: import java.security.MessageDigest;
035: import java.util.ArrayList;
036: import java.util.Arrays;
037: import java.util.Collection;
038:
039: import com.tc.asm.ClassAdapter;
040: import com.tc.asm.ClassVisitor;
041: import com.tc.asm.FieldVisitor;
042: import com.tc.asm.MethodVisitor;
043: import com.tc.asm.Opcodes;
044:
045: /**
046: * A {@link ClassAdapter} that adds a serial version unique identifier to a
047: * class if missing. Here is typical usage of this class:
048: *
049: * <pre>
050: * ClassWriter cw = new ClassWriter(...);
051: * ClassVisitor sv = new SerialVersionUIDAdder(cw);
052: * ClassVisitor ca = new MyClassAdapter(sv);
053: * new ClassReader(orginalClass).accept(ca, false);
054: * </pre>
055: *
056: * The SVUID algorithm can be found <a href=
057: * "http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html"
058: * >http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html</a>:
059: *
060: * <pre>
061: * The serialVersionUID is computed using the signature of a stream of bytes
062: * that reflect the class definition. The National Institute of Standards and
063: * Technology (NIST) Secure Hash Algorithm (SHA-1) is used to compute a
064: * signature for the stream. The first two 32-bit quantities are used to form a
065: * 64-bit hash. A java.lang.DataOutputStream is used to convert primitive data
066: * types to a sequence of bytes. The values input to the stream are defined by
067: * the Java Virtual Machine (VM) specification for classes.
068: *
069: * The sequence of items in the stream is as follows:
070: *
071: * 1. The class name written using UTF encoding.
072: * 2. The class modifiers written as a 32-bit integer.
073: * 3. The name of each interface sorted by name written using UTF encoding.
074: * 4. For each field of the class sorted by field name (except private static
075: * and private transient fields):
076: * 1. The name of the field in UTF encoding.
077: * 2. The modifiers of the field written as a 32-bit integer.
078: * 3. The descriptor of the field in UTF encoding
079: * 5. If a class initializer exists, write out the following:
080: * 1. The name of the method, <clinit>, in UTF encoding.
081: * 2. The modifier of the method, java.lang.reflect.Modifier.STATIC,
082: * written as a 32-bit integer.
083: * 3. The descriptor of the method, ()V, in UTF encoding.
084: * 6. For each non-private constructor sorted by method name and signature:
085: * 1. The name of the method, <init>, in UTF encoding.
086: * 2. The modifiers of the method written as a 32-bit integer.
087: * 3. The descriptor of the method in UTF encoding.
088: * 7. For each non-private method sorted by method name and signature:
089: * 1. The name of the method in UTF encoding.
090: * 2. The modifiers of the method written as a 32-bit integer.
091: * 3. The descriptor of the method in UTF encoding.
092: * 8. The SHA-1 algorithm is executed on the stream of bytes produced by
093: * DataOutputStream and produces five 32-bit values sha[0..4].
094: *
095: * 9. The hash value is assembled from the first and second 32-bit values of
096: * the SHA-1 message digest. If the result of the message digest, the five
097: * 32-bit words H0 H1 H2 H3 H4, is in an array of five int values named
098: * sha, the hash value would be computed as follows:
099: *
100: * long hash = ((sha[0] >>> 24) & 0xFF) |
101: * ((sha[0] >>> 16) & 0xFF) << 8 |
102: * ((sha[0] >>> 8) & 0xFF) << 16 |
103: * ((sha[0] >>> 0) & 0xFF) << 24 |
104: * ((sha[1] >>> 24) & 0xFF) << 32 |
105: * ((sha[1] >>> 16) & 0xFF) << 40 |
106: * ((sha[1] >>> 8) & 0xFF) << 48 |
107: * ((sha[1] >>> 0) & 0xFF) << 56;
108: * </pre>
109: *
110: * @author Rajendra Inamdar, Vishal Vishnoi
111: */
112: public class SerialVersionUIDAdder extends ClassAdapter {
113:
114: /**
115: * Flag that indicates if we need to compute SVUID.
116: */
117: protected boolean computeSVUID;
118:
119: /**
120: * Set to true if the class already has SVUID.
121: */
122: protected boolean hasSVUID;
123:
124: /**
125: * Classes access flags.
126: */
127: protected int access;
128:
129: /**
130: * Internal name of the class
131: */
132: protected String name;
133:
134: /**
135: * Interfaces implemented by the class.
136: */
137: protected String[] interfaces;
138:
139: /**
140: * Collection of fields. (except private static and private transient
141: * fields)
142: */
143: protected Collection svuidFields;
144:
145: /**
146: * Set to true if the class has static initializer.
147: */
148: protected boolean hasStaticInitializer;
149:
150: /**
151: * Collection of non-private constructors.
152: */
153: protected Collection svuidConstructors;
154:
155: /**
156: * Collection of non-private methods.
157: */
158: protected Collection svuidMethods;
159:
160: /**
161: * Creates a new {@link SerialVersionUIDAdder}.
162: *
163: * @param cv a {@link ClassVisitor} to which this visitor will delegate
164: * calls.
165: */
166: public SerialVersionUIDAdder(final ClassVisitor cv) {
167: super (cv);
168: svuidFields = new ArrayList();
169: svuidConstructors = new ArrayList();
170: svuidMethods = new ArrayList();
171: }
172:
173: // ------------------------------------------------------------------------
174: // Overriden methods
175: // ------------------------------------------------------------------------
176:
177: /*
178: * Visit class header and get class name, access , and intefraces
179: * informatoin (step 1,2, and 3) for SVUID computation.
180: */
181: public void visit(final int version, final int access,
182: final String name, final String signature,
183: final String super Name, final String[] interfaces) {
184: computeSVUID = (access & Opcodes.ACC_INTERFACE) == 0;
185:
186: if (computeSVUID) {
187: this .name = name;
188: this .access = access;
189: this .interfaces = interfaces;
190: }
191:
192: super .visit(version, access, name, signature, super Name,
193: interfaces);
194: }
195:
196: /*
197: * Visit the methods and get constructor and method information (step 5 and
198: * 7). Also determince if there is a class initializer (step 6).
199: */
200: public MethodVisitor visitMethod(final int access,
201: final String name, final String desc,
202: final String signature, final String[] exceptions) {
203: if (computeSVUID) {
204: if (name.equals("<clinit>")) {
205: hasStaticInitializer = true;
206: }
207: /*
208: * Remembers non private constructors and methods for SVUID
209: * computation For constructor and method modifiers, only the
210: * ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,
211: * ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT and ACC_STRICT flags
212: * are used.
213: */
214: int mods = access
215: & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE
216: | Opcodes.ACC_PROTECTED
217: | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
218: | Opcodes.ACC_SYNCHRONIZED
219: | Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STRICT);
220:
221: // all non private methods
222: if ((access & Opcodes.ACC_PRIVATE) == 0) {
223: if (name.equals("<init>")) {
224: svuidConstructors.add(new Item(name, mods, desc));
225: } else if (!name.equals("<clinit>")) {
226: svuidMethods.add(new Item(name, mods, desc));
227: }
228: }
229: }
230:
231: return cv
232: .visitMethod(access, name, desc, signature, exceptions);
233: }
234:
235: /*
236: * Gets class field information for step 4 of the alogrithm. Also determines
237: * if the class already has a SVUID.
238: */
239: public FieldVisitor visitField(final int access, final String name,
240: final String desc, final String signature,
241: final Object value) {
242: if (computeSVUID) {
243: if (name.equals("serialVersionUID")) {
244: // since the class already has SVUID, we won't be computing it.
245: computeSVUID = false;
246: hasSVUID = true;
247: }
248: /*
249: * Remember field for SVUID computation For field modifiers, only
250: * the ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC,
251: * ACC_FINAL, ACC_VOLATILE, and ACC_TRANSIENT flags are used when
252: * computing serialVersionUID values.
253: */
254: int mods = access
255: & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE
256: | Opcodes.ACC_PROTECTED
257: | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
258: | Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT);
259:
260: if ((access & Opcodes.ACC_PRIVATE) == 0
261: || (access & (Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT)) == 0) {
262: svuidFields.add(new Item(name, mods, desc));
263: }
264: }
265:
266: return super .visitField(access, name, desc, signature, value);
267: }
268:
269: /*
270: * Add the SVUID if class doesn't have one
271: */
272: public void visitEnd() {
273: // compute SVUID and add it to the class
274: if (computeSVUID && !hasSVUID) {
275: try {
276: cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
277: "serialVersionUID", "J", null, new Long(
278: computeSVUID()));
279: } catch (Throwable e) {
280: throw new RuntimeException(
281: "Error while computing SVUID for " + name, e);
282: }
283: }
284:
285: super .visitEnd();
286: }
287:
288: // ------------------------------------------------------------------------
289: // Utility methods
290: // ------------------------------------------------------------------------
291:
292: /**
293: * Returns the value of SVUID if the class doesn't have one already. Please
294: * note that 0 is returned if the class already has SVUID, thus use
295: * <code>isHasSVUID</code> to determine if the class already had an SVUID.
296: *
297: * @return Returns the serial version UID
298: * @throws IOException
299: */
300: protected long computeSVUID() throws IOException {
301: ByteArrayOutputStream bos = null;
302: DataOutputStream dos = null;
303: long svuid = 0;
304:
305: try {
306: bos = new ByteArrayOutputStream();
307: dos = new DataOutputStream(bos);
308:
309: /*
310: * 1. The class name written using UTF encoding.
311: */
312: dos.writeUTF(name.replace('/', '.'));
313:
314: /*
315: * 2. The class modifiers written as a 32-bit integer.
316: */
317: dos
318: .writeInt(access
319: & (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL
320: | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT));
321:
322: /*
323: * 3. The name of each interface sorted by name written using UTF
324: * encoding.
325: */
326: Arrays.sort(interfaces);
327: for (int i = 0; i < interfaces.length; i++) {
328: dos.writeUTF(interfaces[i].replace('/', '.'));
329: }
330:
331: /*
332: * 4. For each field of the class sorted by field name (except
333: * private static and private transient fields):
334: *
335: * 1. The name of the field in UTF encoding. 2. The modifiers of the
336: * field written as a 32-bit integer. 3. The descriptor of the field
337: * in UTF encoding
338: *
339: * Note that field signatutes are not dot separated. Method and
340: * constructor signatures are dot separated. Go figure...
341: */
342: writeItems(svuidFields, dos, false);
343:
344: /*
345: * 5. If a class initializer exists, write out the following: 1. The
346: * name of the method, <clinit>, in UTF encoding. 2. The modifier of
347: * the method, java.lang.reflect.Modifier.STATIC, written as a
348: * 32-bit integer. 3. The descriptor of the method, ()V, in UTF
349: * encoding.
350: */
351: if (hasStaticInitializer) {
352: dos.writeUTF("<clinit>");
353: dos.writeInt(Opcodes.ACC_STATIC);
354: dos.writeUTF("()V");
355: } // if..
356:
357: /*
358: * 6. For each non-private constructor sorted by method name and
359: * signature: 1. The name of the method, <init>, in UTF encoding. 2.
360: * The modifiers of the method written as a 32-bit integer. 3. The
361: * descriptor of the method in UTF encoding.
362: */
363: writeItems(svuidConstructors, dos, true);
364:
365: /*
366: * 7. For each non-private method sorted by method name and
367: * signature: 1. The name of the method in UTF encoding. 2. The
368: * modifiers of the method written as a 32-bit integer. 3. The
369: * descriptor of the method in UTF encoding.
370: */
371: writeItems(svuidMethods, dos, true);
372:
373: dos.flush();
374:
375: /*
376: * 8. The SHA-1 algorithm is executed on the stream of bytes
377: * produced by DataOutputStream and produces five 32-bit values
378: * sha[0..4].
379: */
380: byte[] hashBytes = computeSHAdigest(bos.toByteArray());
381:
382: /*
383: * 9. The hash value is assembled from the first and second 32-bit
384: * values of the SHA-1 message digest. If the result of the message
385: * digest, the five 32-bit words H0 H1 H2 H3 H4, is in an array of
386: * five int values named sha, the hash value would be computed as
387: * follows:
388: *
389: * long hash = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) <<
390: * 8 | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) <<
391: * 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) & 0xFF) <<
392: * 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] >>> 0) & 0xFF) <<
393: * 56;
394: */
395: for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
396: svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
397: }
398: } finally {
399: // close the stream (if open)
400: if (dos != null) {
401: dos.close();
402: }
403: }
404:
405: return svuid;
406: }
407:
408: /**
409: * Returns the SHA-1 message digest of the given value.
410: *
411: * @param value the value whose SHA message digest must be computed.
412: * @return the SHA-1 message digest of the given value.
413: */
414: protected byte[] computeSHAdigest(final byte[] value) {
415: try {
416: return MessageDigest.getInstance("SHA").digest(value);
417: } catch (Exception e) {
418: throw new UnsupportedOperationException(e.toString());
419: }
420: }
421:
422: /**
423: * Sorts the items in the collection and writes it to the data output stream
424: *
425: * @param itemCollection collection of items
426: * @param dos a <code>DataOutputStream</code> value
427: * @param dotted a <code>boolean</code> value
428: * @exception IOException if an error occurs
429: */
430: private void writeItems(final Collection itemCollection,
431: final DataOutputStream dos, final boolean dotted)
432: throws IOException {
433: int size = itemCollection.size();
434: Item items[] = (Item[]) itemCollection.toArray(new Item[size]);
435: Arrays.sort(items);
436: for (int i = 0; i < size; i++) {
437: dos.writeUTF(items[i].name);
438: dos.writeInt(items[i].access);
439: dos.writeUTF(dotted ? items[i].desc.replace('/', '.')
440: : items[i].desc);
441: }
442: }
443:
444: // ------------------------------------------------------------------------
445: // Inner classes
446: // ------------------------------------------------------------------------
447:
448: static class Item implements Comparable {
449:
450: String name;
451:
452: int access;
453:
454: String desc;
455:
456: Item(final String name, final int access, final String desc) {
457: this .name = name;
458: this .access = access;
459: this .desc = desc;
460: }
461:
462: public int compareTo(final Object o) {
463: Item other = (Item) o;
464: int retVal = name.compareTo(other.name);
465: if (retVal == 0) {
466: retVal = desc.compareTo(other.desc);
467: }
468: return retVal;
469: }
470: }
471: }
|