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
127 if (mayNeedSerialVersionUid(access)) {
128 m_className = name;
129 m_access = access;
130 m_interfaces = interfaces;
131 }
132
133
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
145 if (m_computeSVUID) {
146
147
148 if (name.equals(CLINIT)) {
149 m_hasStaticInitializer = true;
150 } else {
151
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
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
174 if (m_computeSVUID) {
175
176
177 if (name.equals(SVUID_NAME)) {
178 m_hadSVUID = true;
179
180 m_computeSVUID = false;
181 m_SVUID = ((Long) value).longValue();
182 }
183
184
185
186
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
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
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
215 super.visitEnd();
216 }
217
218 protected boolean mayNeedSerialVersionUid(int access) {
219 return true;
220
221
222
223
224
225
226
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
248
249 dos.writeUTF(m_className.replace('/', '.'));
250
251
252
253
254 int classMods = m_access & (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT);
255 dos.writeInt(classMods);
256
257
258
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
268
269
270
271
272
273
274
275
276
277 writeItems(m_svuidFields, dos, false);
278
279
280
281
282
283
284
285
286 if (m_hasStaticInitializer) {
287 dos.writeUTF("<clinit>");
288 dos.writeInt(ACC_STATIC);
289 dos.writeUTF("()V");
290 }
291
292
293
294
295
296
297
298 writeItems(m_svuidConstructors, dos, true);
299
300
301
302
303
304
305
306 writeItems(m_svuidMethods, dos, true);
307
308 dos.flush();
309
310
311
312
313
314 MessageDigest md = MessageDigest.getInstance("SHA");
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
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
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
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 }