View Javadoc

1   /***************************************************************************************
2    * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
3    * http://aspectwerkz.codehaus.org                                                    *
4    * ---------------------------------------------------------------------------------- *
5    * The software in this package is published under the terms of the LGPL license      *
6    * a copy of which has been included with this distribution in the license.txt file.  *
7    **************************************************************************************/
8   package org.codehaus.aspectwerkz.transform.inlining.weaver;
9   
10  import org.objectweb.asm.ClassVisitor;
11  import org.objectweb.asm.ClassAdapter;
12  import org.objectweb.asm.Constants;
13  import org.objectweb.asm.CodeVisitor;
14  import org.objectweb.asm.Attribute;
15  import org.objectweb.asm.ClassReader;
16  import org.objectweb.asm.ClassWriter;
17  import org.codehaus.aspectwerkz.transform.Context;
18  import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
19  import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
20  import org.codehaus.aspectwerkz.reflect.ClassInfo;
21  import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
22  
23  import java.io.IOException;
24  import java.io.DataOutputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.util.Collection;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.MessageDigest;
31  
32  /***
33   * See http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/class.html#60
34   * <p/>
35   * The SerialVersionUidVisitor lookups for the serial ver uid and compute it when not found.
36   * See Add and Compute subclasses.
37   *
38   * Initial implementation courtesy of Vishal Vishnoi <vvishnoi AT bea DOT com>
39   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
40   */
41  public class SerialVersionUidVisitor extends ClassAdapter implements Constants {
42  
43      public static final String CLINIT = "<clinit>";
44      public static final String INIT = "<init>";
45      public static final String SVUID_NAME = "serialVersionUID";
46  
47      /***
48       * flag that indicates if we need to compute SVUID (no need for interfaces)
49       */
50      protected boolean m_computeSVUID = true;
51  
52      /***
53       * Set to true if the class already has SVUID
54       */
55      protected boolean m_hadSVUID = false;
56  
57      /***
58       * The SVUID value (valid at the end of the visit only ie the one that was present or the computed one)
59       */
60      protected long m_SVUID;
61  
62      /***
63       * Internal name of the class
64       */
65      protected String m_className;
66  
67      /***
68       * Classes access flag
69       */
70      protected int m_access;
71  
72      /***
73       * Interfaces implemented by the class
74       */
75      protected String[] m_interfaces;
76  
77      /***
78       * Collection of fields. (except private static
79       * and private transient fields)
80       */
81      protected Collection m_svuidFields = new ArrayList();
82  
83      /***
84       * Set to true if the class has static initializer
85       */
86      protected boolean m_hasStaticInitializer = false;
87  
88      /***
89       * Collection of non private constructors.
90       */
91      protected Collection m_svuidConstructors = new ArrayList();
92  
93      /***
94       * Collection of non private method
95       */
96      protected Collection m_svuidMethods = new ArrayList();
97  
98      /***
99       * 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,
124                       String name, String superName,
125                       String[] interfaces, String sourceFile) {
126         // get SVUID info. only if check passes
127         if (mayNeedSerialVersionUid(access)) {
128             m_className = name;
129             m_access = access;
130             m_interfaces = interfaces;
131         }
132 
133         // delegate call to class visitor
134         super.visit(version, access, name, superName, interfaces, 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,
142                                    String name, String desc,
143                                    String[] exceptions, Attribute attrs) {
144         // get SVUI info
145         if (m_computeSVUID) {
146 
147             // class initialized
148             if (name.equals(CLINIT)) {
149                 m_hasStaticInitializer = true;
150             } else {
151                 // Remember non private constructors and methods for SVUID computation later.
152                 if ((access & ACC_PRIVATE) == 0) {
153                     if (name.equals(INIT)) {
154                         m_svuidConstructors.add(new MethodItem(name, access, desc));
155                     } else {
156                         m_svuidMethods.add(new MethodItem(name, access, desc));
157                     }
158                 }
159             }
160 
161         }
162 
163         // delegate call to class visitor
164         return cv.visitMethod(access, name, desc, exceptions, attrs);
165     }
166 
167     /***
168      * Gets class field information for step 4 of the alogrithm. Also determines
169      * if the class already has a SVUID.
170      */
171     public void visitField(int access, String name, String desc,
172                            Object value, Attribute attrs) {
173         // get SVUID info
174         if (m_computeSVUID) {
175 
176             // check SVUID
177             if (name.equals(SVUID_NAME)) {
178                 m_hadSVUID = true;
179                 // we then don't need to compute it actually
180                 m_computeSVUID = false;
181                 m_SVUID = ((Long) value).longValue();
182             }
183 
184             /*
185              * Remember field for SVUID computation later.
186              * except private static and private transient fields
187              */
188             if (((access & ACC_PRIVATE) == 0) ||
189                     ((access & (ACC_STATIC | ACC_TRANSIENT)) == 0)) {
190                 m_svuidFields.add(new FieldItem(name, access, desc));
191             }
192 
193         }
194 
195         // delegate call to class visitor
196         super.visitField(access, name, desc, value, attrs);
197     }
198 
199     /***
200      * Add the SVUID if class doesn't have one
201      */
202     public void visitEnd() {
203         if (m_computeSVUID) {
204             // compute SVUID if the class doesn't have one
205             if (!m_hadSVUID) {
206                 try {
207                     m_SVUID = computeSVUID();
208                 } catch (Throwable e) {
209                     throw new RuntimeException("Error while computing SVUID for " + m_className, e);
210                 }
211             }
212         }
213 
214         // delegate call to class visitor
215         super.visitEnd();
216     }
217 
218     protected boolean mayNeedSerialVersionUid(int access) {
219         return true;
220         // we don't need to compute SVUID for interfaces //TODO why ???
221 //            if ((access & ACC_INTERFACE) == ACC_INTERFACE) {
222 //                m_computeSVUID = false;
223 //            } else {
224 //                m_computeSVUID = true;
225 //            }
226 //            return m_computeSVUID;
227     }
228 
229     /***
230      * Returns the value of SVUID if the class doesn't have one already. Please
231      * note that 0 is returned if the class already has SVUID, thus use
232      * <code>isHasSVUID</code> to determine if the class already had an SVUID.
233      *
234      * @return Returns the serila version UID
235      */
236     protected long computeSVUID() throws IOException, NoSuchAlgorithmException {
237         ByteArrayOutputStream bos = null;
238         DataOutputStream dos = null;
239         long svuid = 0;
240 
241         try {
242 
243             bos = new ByteArrayOutputStream();
244             dos = new DataOutputStream(bos);
245 
246             /*
247               1. The class name written using UTF encoding.
248             */
249             dos.writeUTF(m_className.replace('/', '.'));
250 
251             /*
252               2. The class modifiers written as a 32-bit integer.
253             */
254             int classMods = m_access & (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,
356                               boolean dotted) throws IOException {
357         int size = itemCollection.size();
358         Item items[] = new Item[size];
359         items = (Item[]) itemCollection.toArray(items);
360         Arrays.sort(items);
361 
362         for (int i = 0; i < size; i++) {
363             items[i].write(dos, dotted);
364         }
365     }
366 
367     /***
368      * An Item represent a field / method / constructor needed in the computation
369      */
370     private static abstract class Item implements Comparable {
371         private String m_name;
372         private int m_access;
373         private String m_desc;
374 
375         Item(String name, int access, String desc) {
376             m_name = name;
377             m_access = access;
378             m_desc = desc;
379         }
380 
381         // see spec, modifiers must be filtered
382         protected abstract int filterAccess(int access);
383 
384         public int compareTo(Object o) {
385             Item other = (Item) o;
386             int retVal = m_name.compareTo(other.m_name);
387             if (retVal == 0) {
388                 retVal = m_desc.compareTo(other.m_desc);
389             }
390             return retVal;
391         }
392 
393         void write(DataOutputStream dos, boolean dotted) 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     private 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 & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
414                     | ACC_VOLATILE | ACC_TRANSIENT);
415         }
416     }
417 
418     /***
419      * A method / constructor item
420      */
421     private static class MethodItem extends Item {
422         MethodItem(String name, int access, String desc) {
423             super(name, access, desc);
424         }
425 
426         protected int filterAccess(int access) {
427             return access & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
428                     | ACC_SYNCHRONIZED | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT);
429         }
430     }
431 
432     /***
433      * Add the serial version uid to the class if not already present
434      *
435      * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
436      */
437     public static class Add extends ClassAdapter {
438 
439         private ContextImpl m_ctx;
440         private ClassInfo m_classInfo;
441 
442         public Add(ClassVisitor classVisitor, Context ctx, ClassInfo classInfo) {
443             super(classVisitor);
444             m_ctx = (ContextImpl) ctx;
445             m_classInfo = classInfo;
446         }
447 
448         public void visitEnd() {
449             if (ClassInfoHelper.implementsInterface(m_classInfo, "java.io.Serializable")) {
450                 ClassReader cr = new ClassReader(m_ctx.getInitialBytecode());
451                 ClassWriter cw = AsmHelper.newClassWriter(true);
452                 SerialVersionUidVisitor sv = new SerialVersionUidVisitor(cw);
453                 cr.accept(sv, true);
454                 if (sv.m_computeSVUID && !sv.m_hadSVUID) {
455                     cv.visitField(ACC_FINAL + ACC_STATIC, SVUID_NAME, "J", new Long(sv.m_SVUID), null);
456                 }
457             }
458             super.visitEnd();
459         }
460     }
461 }