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 }