1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist;
17
18import java.io.*;
19import java.lang.reflect.Modifier;
20
21import javassist.bytecode.*;
22import java.util.*;
23import java.security.*;
24
25/**
26 * Utility for calculating serialVersionUIDs for Serializable classes.
27 *
28 * @author Bob Lee (crazybob@crazybob.org)
29 * @author modified by Shigeru Chiba
30 */
31public class SerialVersionUID {
32
33    /**
34     * Adds serialVersionUID if one does not already exist. Call this before
35     * modifying a class to maintain serialization compatability.
36     */
37    public static void setSerialVersionUID(CtClass clazz)
38        throws CannotCompileException, NotFoundException
39    {
40        // check for pre-existing field.
41        try {
42            clazz.getDeclaredField("serialVersionUID");
43            return;
44        }
45        catch (NotFoundException e) {}
46
47        // check if the class is serializable.
48        if (!isSerializable(clazz))
49            return;
50
51        // add field with default value.
52        CtField field = new CtField(CtClass.longType, "serialVersionUID",
53                                    clazz);
54        field.setModifiers(Modifier.PRIVATE | Modifier.STATIC |
55                           Modifier.FINAL);
56        clazz.addField(field, calculateDefault(clazz) + "L");
57    }
58
59    /**
60     * Does the class implement Serializable?
61     */
62    private static boolean isSerializable(CtClass clazz)
63        throws NotFoundException
64    {
65        ClassPool pool = clazz.getClassPool();
66        return clazz.subtypeOf(pool.get("java.io.Serializable"));
67    }
68
69    /**
70     * Calculate default value. See Java Serialization Specification, Stream
71     * Unique Identifiers.
72     */
73    static long calculateDefault(CtClass clazz)
74        throws CannotCompileException
75    {
76        try {
77            ByteArrayOutputStream bout = new ByteArrayOutputStream();
78            DataOutputStream out = new DataOutputStream(bout);
79            ClassFile classFile = clazz.getClassFile();
80
81            // class name.
82            String javaName = javaName(clazz);
83            out.writeUTF(javaName);
84
85            CtMethod[] methods = clazz.getDeclaredMethods();
86
87            // class modifiers.
88            int classMods = clazz.getModifiers();
89            if ((classMods & Modifier.INTERFACE) != 0)
90                if (methods.length > 0)
91                    classMods = classMods | Modifier.ABSTRACT;
92                else
93                    classMods = classMods & ~Modifier.ABSTRACT;
94
95            out.writeInt(classMods);
96
97            // interfaces.
98            String[] interfaces = classFile.getInterfaces();
99            for (int i = 0; i < interfaces.length; i++)
100                interfaces[i] = javaName(interfaces[i]);
101
102            Arrays.sort(interfaces);
103            for (int i = 0; i < interfaces.length; i++)
104                out.writeUTF(interfaces[i]);
105
106            // fields.
107            CtField[] fields = clazz.getDeclaredFields();
108            Arrays.sort(fields, new Comparator() {
109                public int compare(Object o1, Object o2) {
110                    CtField field1 = (CtField)o1;
111                    CtField field2 = (CtField)o2;
112                    return field1.getName().compareTo(field2.getName());
113                }
114            });
115
116            for (int i = 0; i < fields.length; i++) {
117                CtField field = (CtField) fields[i];
118                int mods = field.getModifiers();
119                if (((mods & Modifier.PRIVATE) == 0) ||
120                    ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) {
121                    out.writeUTF(field.getName());
122                    out.writeInt(mods);
123                    out.writeUTF(field.getFieldInfo2().getDescriptor());
124                }
125            }
126
127            // static initializer.
128            if (classFile.getStaticInitializer() != null) {
129                out.writeUTF("<clinit>");
130                out.writeInt(Modifier.STATIC);
131                out.writeUTF("()V");
132            }
133
134            // constructors.
135            CtConstructor[] constructors = clazz.getDeclaredConstructors();
136            Arrays.sort(constructors, new Comparator() {
137                public int compare(Object o1, Object o2) {
138                    CtConstructor c1 = (CtConstructor)o1;
139                    CtConstructor c2 = (CtConstructor)o2;
140                    return c1.getMethodInfo2().getDescriptor().compareTo(
141                                        c2.getMethodInfo2().getDescriptor());
142                }
143            });
144
145            for (int i = 0; i < constructors.length; i++) {
146                CtConstructor constructor = constructors[i];
147                int mods = constructor.getModifiers();
148                if ((mods & Modifier.PRIVATE) == 0) {
149                    out.writeUTF("<init>");
150                    out.writeInt(mods);
151                    out.writeUTF(constructor.getMethodInfo2()
152                                 .getDescriptor().replace('/', '.'));
153                }
154            }
155
156            // methods.
157            Arrays.sort(methods, new Comparator() {
158                public int compare(Object o1, Object o2) {
159                    CtMethod m1 = (CtMethod)o1;
160                    CtMethod m2 = (CtMethod)o2;
161                    int value = m1.getName().compareTo(m2.getName());
162                    if (value == 0)
163                        value = m1.getMethodInfo2().getDescriptor()
164                            .compareTo(m2.getMethodInfo2().getDescriptor());
165
166                    return value;
167                }
168            });
169
170            for (int i = 0; i < methods.length; i++) {
171                CtMethod method = methods[i];
172                int mods = method.getModifiers()
173                           & (Modifier.PUBLIC | Modifier.PRIVATE
174                              | Modifier.PROTECTED | Modifier.STATIC
175                              | Modifier.FINAL | Modifier.SYNCHRONIZED
176                              | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT);
177                if ((mods & Modifier.PRIVATE) == 0) {
178                    out.writeUTF(method.getName());
179                    out.writeInt(mods);
180                    out.writeUTF(method.getMethodInfo2()
181                                 .getDescriptor().replace('/', '.'));
182                }
183            }
184
185            // calculate hash.
186            out.flush();
187            MessageDigest digest = MessageDigest.getInstance("SHA");
188            byte[] digested = digest.digest(bout.toByteArray());
189            long hash = 0;
190            for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--)
191                hash = (hash << 8) | (digested[i] & 0xFF);
192
193            return hash;
194        }
195        catch (IOException e) {
196            throw new CannotCompileException(e);
197        }
198        catch (NoSuchAlgorithmException e) {
199            throw new CannotCompileException(e);
200        }
201    }
202
203    private static String javaName(CtClass clazz) {
204        return Descriptor.toJavaName(Descriptor.toJvmName(clazz));
205    }
206
207    private static String javaName(String name) {
208        return Descriptor.toJavaName(Descriptor.toJvmName(name));
209    }
210}
211