001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tc.aspectwerkz.transform.inlining.weaver;
005:
006: import com.tc.asm.*;
007:
008: import com.tc.aspectwerkz.transform.InstrumentationContext;
009: import com.tc.aspectwerkz.transform.inlining.AsmHelper;
010: import com.tc.aspectwerkz.reflect.ClassInfo;
011: import com.tc.aspectwerkz.reflect.ClassInfoHelper;
012:
013: import java.io.IOException;
014: import java.io.DataOutputStream;
015: import java.io.ByteArrayOutputStream;
016: import java.util.Collection;
017: import java.util.ArrayList;
018: import java.util.Arrays;
019: import java.security.NoSuchAlgorithmException;
020: import java.security.MessageDigest;
021:
022: /**
023: * See http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/class.html#60
024: * <p/>
025: * The SerialVersionUidVisitor lookups for the serial ver uid and compute it when not found.
026: * See Add and Compute subclasses.
027: * <p/>
028: * Initial implementation courtesy of Vishal Vishnoi <vvishnoi AT bea DOT com>
029: *
030: * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
031: */
032: public class SerialVersionUidVisitor extends ClassAdapter implements
033: Opcodes {
034:
035: public static final String CLINIT = "<clinit>";
036: public static final String INIT = "<init>";
037: public static final String SVUID_NAME = "serialVersionUID";
038:
039: /**
040: * flag that indicates if we need to compute SVUID (no need for interfaces)
041: */
042: protected boolean m_computeSVUID = true;
043:
044: /**
045: * Set to true if the class already has SVUID
046: */
047: protected boolean m_hadSVUID = false;
048:
049: /**
050: * The SVUID value (valid at the end of the visit only ie the one that was present or the computed one)
051: */
052: protected long m_SVUID;
053:
054: /**
055: * Internal name of the class
056: */
057: protected String m_className;
058:
059: /**
060: * Classes access flag
061: */
062: protected int m_access;
063:
064: /**
065: * Interfaces implemented by the class
066: */
067: protected String[] m_interfaces;
068:
069: /**
070: * Collection of fields. (except private static
071: * and private transient fields)
072: */
073: protected Collection m_svuidFields = new ArrayList();
074:
075: /**
076: * Set to true if the class has static initializer
077: */
078: protected boolean m_hasStaticInitializer = false;
079:
080: /**
081: * Collection of non private constructors.
082: */
083: protected Collection m_svuidConstructors = new ArrayList();
084:
085: /**
086: * Collection of non private method
087: */
088: protected Collection m_svuidMethods = new ArrayList();
089:
090: /**
091: * helper method (test purpose)
092: *
093: * @param klass
094: * @return
095: */
096: public static long calculateSerialVersionUID(Class klass) {
097: try {
098: ClassReader cr = new ClassReader(klass.getName());
099: ClassWriter cw = AsmHelper.newClassWriter(true);
100: SerialVersionUidVisitor sv = new SerialVersionUidVisitor(cw);
101: cr.accept(sv, ClassReader.SKIP_DEBUG
102: | ClassReader.SKIP_FRAMES);
103: return sv.m_SVUID;
104: } catch (IOException e) {
105: throw new RuntimeException(e);
106: }
107: }
108:
109: SerialVersionUidVisitor(final ClassVisitor cv) {
110: super (cv);
111: }
112:
113: /**
114: * Visit class header and getDefault class name, access , and interfaces information
115: * (step 1,2, and 3) for SVUID computation.
116: */
117: public void visit(int version, int access, String name,
118: String signature, String super Name, String[] interfaces) {
119: // getDefault SVUID info. only if check passes
120: if (mayNeedSerialVersionUid(access)) {
121: m_className = name;
122: m_access = access;
123: m_interfaces = interfaces;
124: }
125:
126: // delegate call to class visitor
127: super .visit(version, access, name, signature, super Name,
128: interfaces);
129: }
130:
131: /**
132: * Visit the methods and getDefault constructor and method information (step
133: * 5 and 7). Also determince if there is a class initializer (step 6).
134: */
135: public MethodVisitor visitMethod(int access, String name,
136: String desc, String signature, String[] exceptions) {
137: // getDefault SVUI info
138: if (m_computeSVUID) {
139:
140: // class initialized
141: if (name.equals(CLINIT)) {
142: m_hasStaticInitializer = true;
143: } else {
144: // Remember non private constructors and methods for SVUID computation later.
145: if ((access & ACC_PRIVATE) == 0) {
146: if (name.equals(INIT)) {
147: m_svuidConstructors.add(new MethodItem(name,
148: access, desc));
149: } else {
150: m_svuidMethods.add(new MethodItem(name, access,
151: desc));
152: }
153: }
154: }
155:
156: }
157:
158: // delegate call to class visitor
159: return cv
160: .visitMethod(access, name, desc, signature, exceptions);
161: }
162:
163: /**
164: * Gets class field information for step 4 of the alogrithm. Also determines
165: * if the class already has a SVUID.
166: */
167: public FieldVisitor visitField(int access, String name,
168: String desc, String signature, Object value) {
169: // getDefault SVUID info
170: if (m_computeSVUID) {
171:
172: // check SVUID
173: if (name.equals(SVUID_NAME)) {
174: m_hadSVUID = true;
175: // we then don't need to compute it actually
176: m_computeSVUID = false;
177: m_SVUID = ((Long) value).longValue();
178: }
179:
180: /*
181: * Remember field for SVUID computation later.
182: * except private static and private transient fields
183: */
184: if (((access & ACC_PRIVATE) == 0)
185: || ((access & (ACC_STATIC | ACC_TRANSIENT)) == 0)) {
186: m_svuidFields.add(new FieldItem(name, access, desc));
187: }
188:
189: }
190:
191: // delegate call to class visitor
192: return super .visitField(access, name, desc, signature, value);
193: }
194:
195: /**
196: * Add the SVUID if class doesn't have one
197: */
198: public void visitEnd() {
199: if (m_computeSVUID) {
200: // compute SVUID if the class doesn't have one
201: if (!m_hadSVUID) {
202: try {
203: m_SVUID = computeSVUID();
204: } catch (Throwable e) {
205: throw new RuntimeException(
206: "Error while computing SVUID for "
207: + m_className, e);
208: }
209: }
210: }
211:
212: // delegate call to class visitor
213: super .visitEnd();
214: }
215:
216: protected boolean mayNeedSerialVersionUid(int access) {
217: return true;
218: // we don't need to compute SVUID for interfaces //TODO why ???
219: // if ((access & ACC_INTERFACE) == ACC_INTERFACE) {
220: // m_computeSVUID = false;
221: // } else {
222: // m_computeSVUID = true;
223: // }
224: // return m_computeSVUID;
225: }
226:
227: /**
228: * Returns the value of SVUID if the class doesn't have one already. Please
229: * note that 0 is returned if the class already has SVUID, thus use
230: * <code>isHasSVUID</code> to determine if the class already had an SVUID.
231: *
232: * @return Returns the serila version UID
233: */
234: protected long computeSVUID() throws IOException,
235: NoSuchAlgorithmException {
236: ByteArrayOutputStream bos = null;
237: DataOutputStream dos = null;
238: long svuid = 0;
239:
240: try {
241:
242: bos = new ByteArrayOutputStream();
243: dos = new DataOutputStream(bos);
244:
245: /*
246: 1. The class name written using UTF encoding.
247: */
248: dos.writeUTF(m_className.replace('/', '.'));
249:
250: /*
251: 2. The class modifiers written as a 32-bit integer.
252: */
253: int classMods = m_access
254: & (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT);
255: dos.writeInt(classMods);
256:
257: /*
258: 3. The name of each interface sorted by name written using UTF encoding.
259: */
260: Arrays.sort(m_interfaces);
261: for (int i = 0; i < m_interfaces.length; i++) {
262: String ifs = m_interfaces[i].replace('/', '.');
263: dos.writeUTF(ifs);
264: }
265:
266: /*
267: 4. For each field of the class sorted by field name (except private
268: static and private transient fields):
269:
270: 1. The name of the field in UTF encoding.
271: 2. The modifiers of the field written as a 32-bit integer.
272: 3. The descriptor of the field in UTF encoding
273:
274: Note that field signatutes are not dot separated. Method and
275: constructor signatures are dot separated. Go figure...
276: */
277: writeItems(m_svuidFields, dos, false);
278:
279: /*
280: 5. If a class initializer exists, write out the following:
281: 1. The name of the method, <clinit>, in UTF encoding.
282: 2. The modifier of the method, java.lang.reflect.Modifier.STATIC,
283: written as a 32-bit integer.
284: 3. The descriptor of the method, ()V, in UTF encoding.
285: */
286: if (m_hasStaticInitializer) {
287: dos.writeUTF("<clinit>");
288: dos.writeInt(ACC_STATIC);
289: dos.writeUTF("()V");
290: }
291:
292: /*
293: 6. For each non-private constructor sorted by method name and signature:
294: 1. The name of the method, <init>, in UTF encoding.
295: 2. The modifiers of the method written as a 32-bit integer.
296: 3. The descriptor of the method in UTF encoding.
297: */
298: writeItems(m_svuidConstructors, dos, true);
299:
300: /*
301: 7. For each non-private method sorted by method name and signature:
302: 1. The name of the method in UTF encoding.
303: 2. The modifiers of the method written as a 32-bit integer.
304: 3. The descriptor of the method in UTF encoding.
305: */
306: writeItems(m_svuidMethods, dos, true);
307:
308: dos.flush();
309:
310: /*
311: 8. The SHA-1 algorithm is executed on the stream of bytes produced by
312: DataOutputStream and produces five 32-bit values sha[0..4].
313: */
314: MessageDigest md = MessageDigest.getInstance("SHA");
315:
316: /*
317: 9. The hash value is assembled from the first and second 32-bit values
318: of the SHA-1 message digest. If the result of the message digest, the
319: five 32-bit words H0 H1 H2 H3 H4, is in an array of five int values
320: named sha, the hash value would be computed as follows:
321:
322: long hash = ((sha[0] >>> 24) & 0xFF) |
323: ((sha[0] >>> 16) & 0xFF) << 8 |
324: ((sha[0] >>> 8) & 0xFF) << 16 |
325: ((sha[0] >>> 0) & 0xFF) << 24 |
326: ((sha[1] >>> 24) & 0xFF) << 32 |
327: ((sha[1] >>> 16) & 0xFF) << 40 |
328: ((sha[1] >>> 8) & 0xFF) << 48 |
329: ((sha[1] >>> 0) & 0xFF) << 56;
330: */
331: byte[] hashBytes = md.digest(bos.toByteArray());
332: for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
333: svuid = (svuid << 8) | (hashBytes[i] & 0xFF);
334: }
335:
336: } finally {
337: // close the stream (if open)
338: if (dos != null) {
339: dos.close();
340: }
341: }
342:
343: return svuid;
344: }
345:
346: /**
347: * Sorts the items in the collection and writes it to the data output stream
348: *
349: * @param itemCollection collection of items
350: * @param dos a <code>DataOutputStream</code> value
351: * @param dotted a <code>boolean</code> value
352: * @throws IOException if an error occurs
353: */
354: protected void writeItems(Collection itemCollection,
355: DataOutputStream dos, boolean dotted) throws IOException {
356: int size = itemCollection.size();
357: Item items[] = new Item[size];
358: items = (Item[]) itemCollection.toArray(items);
359: Arrays.sort(items);
360:
361: for (int i = 0; i < size; i++) {
362: items[i].write(dos, dotted);
363: }
364: }
365:
366: /**
367: * An Item represent a field / method / constructor needed in the computation
368: */
369: static abstract class Item implements Comparable {
370: private String m_name;
371: private int m_access;
372: private String m_desc;
373:
374: Item(String name, int access, String desc) {
375: m_name = name;
376: m_access = access;
377: m_desc = desc;
378: }
379:
380: // see spec, modifiers must be filtered
381: protected abstract int filterAccess(int access);
382:
383: public int compareTo(Object o) {
384: Item other = (Item) o;
385: int retVal = m_name.compareTo(other.m_name);
386: if (retVal == 0) {
387: retVal = m_desc.compareTo(other.m_desc);
388: }
389: return retVal;
390: }
391:
392: void write(DataOutputStream dos, boolean dotted)
393: throws IOException {
394: dos.writeUTF(m_name);
395: dos.writeInt(filterAccess(m_access));
396: if (dotted) {
397: dos.writeUTF(m_desc.replace('/', '.'));
398: } else {
399: dos.writeUTF(m_desc);
400: }
401: }
402: }
403:
404: /**
405: * A field item
406: */
407: static class FieldItem extends Item {
408: FieldItem(String name, int access, String desc) {
409: super (name, access, desc);
410: }
411:
412: protected int filterAccess(int access) {
413: return access
414: & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED
415: | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT);
416: }
417: }
418:
419: /**
420: * A method / constructor item
421: */
422: static class MethodItem extends Item {
423: MethodItem(String name, int access, String desc) {
424: super (name, access, desc);
425: }
426:
427: protected int filterAccess(int access) {
428: return access
429: & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED
430: | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED
431: | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT);
432: }
433: }
434:
435: /**
436: * Add the serial version uid to the class if not already present
437: */
438: public static class Add extends ClassAdapter {
439:
440: private InstrumentationContext m_ctx;
441: private ClassInfo m_classInfo;
442:
443: public Add(ClassVisitor classVisitor,
444: InstrumentationContext ctx, ClassInfo classInfo) {
445: super (classVisitor);
446: m_ctx = ctx;
447: m_classInfo = classInfo;
448: }
449:
450: public void visitEnd() {
451: if (ClassInfoHelper.implements Interface(m_classInfo,
452: "java.io.Serializable")) {
453: ClassReader cr = new ClassReader(m_ctx
454: .getInitialBytecode());
455: ClassWriter cw = AsmHelper.newClassWriter(true);
456: SerialVersionUidVisitor sv = new SerialVersionUidVisitor(
457: cw);
458: cr.accept(sv, ClassReader.SKIP_DEBUG
459: | ClassReader.SKIP_FRAMES);
460: if (sv.m_computeSVUID && !sv.m_hadSVUID) {
461: cv.visitField(ACC_FINAL + ACC_STATIC, SVUID_NAME,
462: "J", null, new Long(sv.m_SVUID));
463: }
464: }
465: super.visitEnd();
466: }
467: }
468: }
|