/******************************************************************************* * Copyright (c) 2002,2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.shrikeBT.shrikeCT.tools; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Comparator; import com.ibm.wala.shrikeBT.Util; import com.ibm.wala.shrikeCT.ClassReader; import com.ibm.wala.shrikeCT.ClassWriter; import com.ibm.wala.shrikeCT.ConstantValueWriter; import com.ibm.wala.shrikeCT.InvalidClassFileException; public class AddSerialVersion { // this class should not be instantiated private AddSerialVersion() { } /** * This method computes the serialVersionUID for class r (if there isn't one already) and adds the field to the classwriter w. * * When run as a program, just takes a list of class files as command line arguments and computes their serialVersionUIDs. * * @throws IllegalArgumentException if r is null */ public static void addSerialVersionUID(ClassReader r, ClassWriter w) throws InvalidClassFileException { if (r == null) { throw new IllegalArgumentException("r is null"); } int numFields = r.getFieldCount(); for (int i = 0; i < numFields; i++) { if (r.getFieldName(i).equals("serialVersionUID")) { return; // already has a serialVersionUID } } long UID = computeSerialVersionUID(r); w.addField(ClassReader.ACC_PUBLIC | ClassReader.ACC_STATIC | ClassReader.ACC_FINAL, "serialVersionUID", "J", new ClassWriter.Element[] { new ConstantValueWriter(w, UID) }); } /** * This class implements a stream that just discards everything written to it. */ public static final class SinkOutputStream extends OutputStream { @Override public void write(int b) { } @Override public void write(byte[] b) { } @Override public void write(byte[] b, int off, int len) { } } /** * This method computes the serialVersionUID for class r. See the specification at * http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/class.html * * @throws IllegalArgumentException if r is null */ public static long computeSerialVersionUID(final ClassReader r) throws InvalidClassFileException { if (r == null) { throw new IllegalArgumentException("r is null"); } MessageDigest digest; try { digest = MessageDigest.getInstance("SHA"); } catch (NoSuchAlgorithmException e) { throw new Error("SHA algorithm not supported: " + e.getMessage()); } SinkOutputStream sink = new SinkOutputStream(); DataOutputStream out = new DataOutputStream(new DigestOutputStream(sink, digest)); try { // step 1 out.writeUTF(r.getName()); // step 2 out.writeInt(r.getAccessFlags()); // step 3 String[] interfaces = r.getInterfaceNames(); Arrays.sort(interfaces); for (int i = 0; i < interfaces.length; i++) { out.writeUTF(interfaces[i]); } // step 4 Integer[] fields = new Integer[r.getFieldCount()]; final String[] fieldNames = new String[fields.length]; int fieldCount = 0; for (int f = 0; f < fields.length; f++) { int flags = r.getFieldAccessFlags(f); if ((flags & ClassReader.ACC_PRIVATE) == 0 || (flags & (ClassReader.ACC_STATIC | ClassReader.ACC_TRANSIENT)) == 0) { fields[fieldCount] = new Integer(f); fieldNames[f] = r.getFieldName(f); fieldCount++; } } Arrays.sort(fields, 0, fieldCount, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { String name1 = fieldNames[o1.intValue()]; String name2 = fieldNames[o2.intValue()]; return name1.compareTo(name2); } }); for (int i = 0; i < fieldCount; i++) { int f = fields[i].intValue(); out.writeUTF(fieldNames[f]); out.writeInt(r.getFieldAccessFlags(f)); out.writeUTF(r.getFieldType(f)); } // steps 5, 6 and 7 Integer[] methods = new Integer[r.getMethodCount()]; final int[] methodKinds = new int[methods.length]; final String[] methodSigs = new String[methods.length]; int methodCount = 0; for (int m = 0; m < methodSigs.length; m++) { String name = r.getMethodName(m); int flags = r.getMethodAccessFlags(m); if (name.equals("<clinit>") || (flags & ClassReader.ACC_PRIVATE) == 0) { methods[methodCount] = new Integer(m); methodSigs[m] = name + r.getMethodType(m); if (name.equals("<clinit>")) { methodKinds[m] = 0; } else if (name.equals("<init>")) { methodKinds[m] = 1; } else { methodKinds[m] = 2; } methodCount++; } } Arrays.sort(methods, 0, methodCount, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { int m1 = o1.intValue(); int m2 = o2.intValue(); if (methodKinds[m1] != methodKinds[m2]) { return methodKinds[m1] - methodKinds[m2]; } String name1 = methodSigs[m1]; String name2 = methodSigs[m2]; return name1.compareTo(name2); } }); for (int i = 0; i < methodCount; i++) { int m = methods[i].intValue(); out.writeUTF(r.getMethodName(m)); out.writeInt(r.getMethodAccessFlags(m)); out.writeUTF(r.getMethodType(m)); } } catch (IOException e1) { throw new Error("Unexpected IOException: " + e1.getMessage()); } finally { try { out.close(); } catch (IOException e2) { } } byte[] hash = digest.digest(); return (hash[0] & 0xFF) | (hash[1] & 0xFF) << 8 | (hash[2] & 0xFF) << 16 | hash[3] << 24 | (hash[4] & 0xFF) << 32 | (hash[5] & 0xFF) << 40 | (hash[6] & 0xFF) << 48 | (hash[7] & 0xFF) << 56; } public static void main(String[] args) { for (int i = 0; i < args.length; i++) { if (args[i] == null) { throw new IllegalArgumentException("args[" + i + "] is null"); } try { byte[] data = Util.readFully(new FileInputStream(args[i])); ClassReader r = new ClassReader(data); System.out.println(Util.makeClass(r.getName()) + ": serialVersionUID = " + computeSerialVersionUID(r)); } catch (FileNotFoundException e) { System.err.println("File not found: " + args[i]); } catch (IOException e) { System.err.println("Error reading file: " + args[i]); } catch (InvalidClassFileException e) { System.err.println("Invalid class file: " + args[i]); } } } }