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