Hello guys, I had some problem to solve saturday. So I started working on serialver implementation on sunday and finished testing on monday. It's all there, except -show. So I'm attaching the patch (SVN), because I didn't find a jira bug for serialver implementation. I'm having some problems on setting up eclipse to dev, but I'll send them on another email to the list.
Regards, Marcos 2010/3/12 Mark Hindess <mark.hind...@googlemail.com> > > In message <a58adbb51003111635g379c82eep37726b2f3f5a1...@mail.gmail.com>, > Marcos Roriz writes: > > > > Done, I sended the forms filled. > > > > I checked out the code and now I'll try to familiarize with the code, > > any tip, for where should I start to look for the "jdktools" general > > stuff? I've started looking in the samsa "launcher". > > For the record, I had a chat to Marcos last night on IRC and suggested > that he take a look at implementing the 'serialver' jdk tool[0] and > create a JIRA/patch. > > Regards, > -Mark > > [0] Just the command line interface - ignoring the -show GUI for now. > > > -- Marcos Roriz Bacharelando em Ciência da Computação Universidade Federal de Goiás E-mail: marcosroriz...@gmail.com Home Page:http://marcosroriz.wordpress.com
Index: src/main/java/org/apache/harmony/tools/serialver/Clazz.java =================================================================== --- src/main/java/org/apache/harmony/tools/serialver/Clazz.java (revision 0) +++ src/main/java/org/apache/harmony/tools/serialver/Clazz.java (revision 0) @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.tools.serialver; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Vector; + +import org.apache.bcel.classfile.Field; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.harmony.tools.ClassProvider; + +/** + * This class is a wrapper of BCEL's Class class. + * + * This class depends on Apache Byte Code Engineering Library (BCEL) 5.0 or + * later. Please see http://jakarta.apache.org/bcel for more information about + * this library. + */ +public class Clazz { + + /** + * A wrapped class. + */ + private JavaClass wrappedClass; + + /** + * Class fields that will be used to calculate the serialversionUID. All + * fields except private static and private transient. (SORTED) + */ + private Vector<Field> fields; + + /** + * Class constructors that will be used to calculate the serialversionUID. + * Default constructor (if exists) and all non-private constructors. + * (SORTED) + */ + private Vector<Method> constructors; + + /** + * Class methods that will be used to calculate the serialversionUID. All + * non-private methods. (SORTED) + */ + private Vector<Method> methods; + + /** + * Field that represents existance SUID (if-exists). + */ + private Field suidField; + + /** + * Constructs a <code>Clazz</code> object. + * + * @param classProvider + * - a helper that provides the class information. + * @param className + * - a fully qualified name of a class. + * @throws ClassNotFoundException + * - if a give class is not found + */ + public Clazz(ClassProvider classProvider, String className) + throws ClassNotFoundException { + try { + wrappedClass = classProvider.getJavaClass(className); + + // Collect up all valid fields (non transient and private, and non + // static private) and sort it. + fields = sortFields(collectValidFields(wrappedClass)); + + // Collect all valid constructors (non-private) and sort it. + constructors = sortConstructors(collectConstructors(wrappedClass)); + + // Collect all valid methods (non-private) and sort it. + methods = sortMethods(collectMethods(wrappedClass)); + } catch (ClassNotFoundException e) { + throw new ClassNotFoundException(); + } + } + + /** + * Returns a name of a wrapped class. + * + * @return a class name. + */ + public String getName() { + return wrappedClass.getClassName(); + } + + /** + * Returns the {...@link Modifiers} of the wrapped class. + * + * @return a constant int + */ + public int getModifiers() { + return wrappedClass.getModifiers(); + } + + /** + * Get all interfaces names that the wrapped class implement and sort it. + * + * @return interfaces names implemented (sorted). + */ + public String[] getSortedInterfaces() { + String[] ifNames = wrappedClass.getInterfaceNames(); + Arrays.sort(ifNames); + return ifNames; + } + + /** + * Get all valid Fields (i.e., all excluding private static and private + * transient) of the wrapped class and sort it. + * + * @return a Field Vector (Sorted). + */ + public Vector<Field> getSortedValidFields() { + return fields; + } + + /** + * Get all valid Constructors (i.e., non-private) of the wrapped class and + * sort it. + * + * @return a Method Vector (Sorted). + */ + public Vector<Method> getSortedValidConstructors() { + return constructors; + } + + /** + * Get all valid Methods (i.e., non-private) of the wrapped class and sort + * it. + * + * @return a Method Vector (Sorted). + */ + public Vector<Method> getSortedValidMethods() { + return methods; + } + + /** + * Get the existing serialVersionUID of the wrapped class. + * + * @return a long that is the serialVersionUID of the wrapped class. + * @throws NoSuchFieldException + * if the field doesn't exist. + */ + public long getExistantSerialVersionUID() throws NoSuchFieldException { + return Long.parseLong(suidField.getAttributes()[0].toString()); + } + + /** + * Verify if the wrapped class is serializable. + * + * @return true - if it's serializable<br /> + * false otherwise. + * @throws ClassNotFoundException + * if the wrappedclass isn't found. + */ + public boolean isSerializable() throws ClassNotFoundException { + boolean serial = false; + for (JavaClass clazzInterface : wrappedClass.getAllInterfaces()) { + if (clazzInterface.getClassName().equals("java.io.Serializable")) + serial = true; + } + return serial; + } + + /** + * Verify if a give class has a serialVersionUID Field. + * + * @return true - if it has this field<br /> + * false otherwise. + */ + public boolean hasSerialVersionUID() { + return suidField != null; + } + + /** + * Collect valid fields of the wrapped class. Valid fields includes all + * except private static and private transient. + * + * @param clazz + * - the wrapped class. + * @return a {...@link Vector} of valid fields. + */ + private Vector<Field> collectValidFields(JavaClass clazz) { + Field[] allFields = clazz.getFields(); + Vector<Field> validFields = new Vector<Field>(); + suidField = null; + + int PRIVATE_STATIC = Modifier.PRIVATE | Modifier.STATIC; + int PRIVATE_TRANSIENT = Modifier.PRIVATE | Modifier.TRANSIENT; + + // Excluding all PRIVATE_STATIC and PRIVATE_TRANSIENT fields + for (int i = 0; i < allFields.length; i++) { + if ((allFields[i].getModifiers() != PRIVATE_STATIC) + && (allFields[i].getModifiers() != PRIVATE_TRANSIENT)) { + // Adding + validFields.add(allFields[i]); + + // Let's check if the Field is a existing SUID + if (allFields[i].getName().equals("serialVersionUID")) { + suidField = allFields[i]; + } + } + } + return validFields; + } + + /** + * Collect valid constructors of the wrapped class. Valid constructors + * includes all except private. + * + * @param clazz + * - the wrapped class. + * @return a {...@link Vector} of valid constructors. + */ + private Vector<Method> collectConstructors(JavaClass clazz) { + Method methods[] = clazz.getMethods(); + Vector<Method> validConstructors = new Vector<Method>(); + + // Excludes all constructors that are private + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + String mName = m.getName(); + + if (mName.equals("<clinit>") || mName.equals("<init>")) + if (!m.isPrivate()) + validConstructors.add(m); + } + + return validConstructors; + } + + /** + * Collect valid methods of the wrapped class. Valid methods includes all + * except private. + * + * @param clazz + * - the wrapped class. + * @return a {...@link Vector} of valid methods. + */ + private Vector<Method> collectMethods(JavaClass clazz) { + Method methods[] = clazz.getMethods(); + Vector<Method> validMethods = new Vector<Method>(); + + // Excludes all methods that are private + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + String mName = m.getName(); + + // is not a constructor + if (!mName.equals("<clinit>") && !mName.equals("<init>")) { + if (!m.isPrivate()) { + validMethods.add(m); + } + } + } + + return validMethods; + } + + /** + * Sort the valid fields of the wrapped class. + * + * @param vector + * - to be sorted. + * @return a Field Vector sorted. + */ + private Vector<Field> sortFields(Vector<Field> vector) { + Collections.sort(vector, new Comparator<Field>() { + public int compare(Field f1, Field f2) { + return f1.getName().compareTo(f2.getName()); + } + }); + return vector; + } + + /** + * Sort the valid constructors of the wrapped class. + * + * @param vector + * - to be sorted. + * @return a Method Vector sorted. + */ + private Vector<Method> sortConstructors(Vector<Method> vector) { + Collections.sort(vector, new Comparator<Method>() { + public int compare(Method m1, Method m2) { + if (m1.getName() == "<clinit>") + return 1; + + return m1.getSignature().compareTo(m2.getSignature()); + } + }); + + return vector; + } + + /** + * Sort the valid methods of the wrapped class. + * + * @param vector + * - to be sorted. + * @return a Method Vector sorted. + */ + private Vector<Method> sortMethods(Vector<Method> vector) { + Collections.sort(vector, new Comparator<Method>() { + public int compare(Method m1, Method m2) { + int name = m1.getName().compareTo(m2.getName()); + if (name != 0) { + return name; + } else { + return m1.getSignature().compareTo(m2.getSignature()); + } + } + }); + + return vector; + } +} Index: src/main/java/org/apache/harmony/tools/serialver/Main.java =================================================================== --- src/main/java/org/apache/harmony/tools/serialver/Main.java (revision 0) +++ src/main/java/org/apache/harmony/tools/serialver/Main.java (revision 0) @@ -0,0 +1,308 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.tools.serialver; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Modifier; +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; + +import org.apache.bcel.classfile.Field; +import org.apache.bcel.classfile.Method; +import org.apache.harmony.tools.ClassProvider; + +/** + * This is a tool that calculates the serialVersionUID of a serializable class. + */ +public class Main { + + public static void main(String args[]) { + if (args.length < 1) { + usage(); + System.exit(1); + } + + String pathSep = null; + try { + pathSep = System.getProperty("path.separator"); + } catch (SecurityException e) { + // ignore + } + + Vector<String> names = new Vector<String>(); + Map options = new HashMap(); + int i = 0; + while (i < args.length) { + if (args[i].equals("-classpath")) { + try { + String path = (String) args[i + 1]; + + // We can concatenate several options, if there is no + // security restriction; otherwise, the new option replaces + // the old one. + if (pathSep != null) { + String prevPath = (String) options.get(args[i]); + if (prevPath != null) { + path = prevPath + pathSep + path; + } + } + + options.put(args[i], path); + i++; + } catch (ArrayIndexOutOfBoundsException ex) { + System.out + .println("Missing argument for -classpath option"); + usage(); + System.exit(1); + } + } else if (args[i].equals("-show")) { + options.put(args[i], Boolean.valueOf(true)); + } else if (args[i].charAt(0) == '-') { + // invalid flag (only allowed -show and -classpath + System.out.println("Invalid flag " + args[i]); + usage(); + System.exit(1); + } else { + names.add(args[i]); + } + i++; + } + System.exit(run(options, names)); + } + + /** + * Runs a tool. + * + * @param options + * - a <code>java.util.Map</code> of the following key-value + * pairs. + * + * <li><i>key</i> - "-classpath" <li><i>value</i> - a + * <code>java.lang.String</code> which is a path where classes + * are located. + * + * <li><i>key</i> - "-show" <li><i>value</i> - display a simple + * GUI with a textfield, where the user can enter the name of the + * class he wants to generate the serialversionUID. + * + * @param classNames + * - a vector of the fully qualified class names. + * @return <code>0</code> if there is no error; <code>1</code> otherwise. + */ + public static int run(Map options, Vector<String> classNames) { + int returnValue = 0; + + String classpath = getString(options, "-classpath"); + boolean show = getBoolean(options, "-show"); // TODO make show GUI + Iterator namesIter = classNames.iterator(); + + // The user can't type -show and put classnames - SUN Spec + if ((namesIter.hasNext() == true) && (show == true)) { + System.out.println("Cannot specify class arguments with the -show option"); + return 1; + } + + ClassProvider classProvider = new ClassProvider(null, classpath, false); + while (namesIter.hasNext()) { + String className = (String) namesIter.next(); + try { + // Parse the next class. + Clazz clazz = new Clazz(classProvider, className); + + // Let's check if the class has any 'annoiance' + if (!isSerializable(clazz)) { + System.out.println("Class " + clazz.getName() + " is not Serializable."); + continue; + } + + long hash = calculeSUID(clazz); + printResult(clazz, hash); + } catch (Exception e) { + System.out.println("Class " + className + " not found."); + returnValue = 1; + } + } + return returnValue; + } + + /** + * Calculates the serialversionUID for a given {...@link Clazz}. + * + * @param clazz + * - wrapped class + * @return the serialversionUID + * @throws ClassNotFoundException + * if a class is not found + */ + public static long calculeSUID(Clazz clazz) throws ClassNotFoundException { + try { + // Let's check if class has already a suid, if yes, we don't need to calculate + if (clazz.hasSerialVersionUID()) { + return clazz.getExistantSerialVersionUID(); + } + + // Let's compute the serialversionUID + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream daos = new DataOutputStream(baos); + + // Class name + daos.writeUTF(clazz.getName()); + + // Class Modifier + int classMask = Modifier.PUBLIC | Modifier.FINAL + | Modifier.INTERFACE | Modifier.ABSTRACT; + daos.writeInt(clazz.getModifiers() & classMask); + + // Write all interfaces name (Sorted) + for (String clazzInterface : clazz.getSortedInterfaces()) { + daos.writeUTF(clazzInterface); + } + + // We need to write the field/constructor/method information in + // the following order: + // 1 - Name + // 2 - Modifier + // 3 - Signature + + // Only valid fields, i.e. , all fields except + // static private and transient private + int fieldMask = Modifier.PUBLIC | Modifier.PRIVATE + | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL + | Modifier.VOLATILE | Modifier.TRANSIENT; + + for (Field field : clazz.getSortedValidFields()) { + daos.writeUTF(field.getName()); + daos.writeInt(field.getModifiers() & fieldMask); + daos.writeUTF(field.getSignature()); + } + + // Only valid Constructors, i.e., all non-private + // The first constructor must be the default ()V [if-exists] + // The method already takes cares of this + int methodMask = Modifier.PUBLIC | Modifier.PRIVATE + | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL + | Modifier.SYNCHRONIZED | Modifier.NATIVE + | Modifier.ABSTRACT | Modifier.STRICT; + + for (Method constructor : clazz.getSortedValidConstructors()) { + daos.writeUTF(constructor.getName()); + daos.writeInt(constructor.getModifiers() & methodMask); + daos.writeUTF(constructor.getSignature()); + } + + // Only valid Methods, i.e., all non-private + for (Method methods : clazz.getSortedValidMethods()) { + daos.writeUTF(methods.getName()); + daos.writeInt(methods.getModifiers() & methodMask); + daos.writeUTF(methods.getSignature().replace('/', '.')); + } + + // Hash is calculated the following way: + // Get the 8 [0 ... 7] bytes and invert their order, i.e, + // Suppose bytes are 00110011 ... 11110000, the final order + // would be: + // Result: 11110000 ... 00110011 + MessageDigest md = MessageDigest.getInstance("SHA"); + byte[] sha = md.digest(baos.toByteArray()); + long hash = 0; + + for (int i = 7; i >= 0; i--) { + hash = (hash << 8) | (sha[i] & 0xFF); + } + + return hash; + } catch (Exception e) { + throw new ClassNotFoundException(); + } + } + + /** + * Verify if the wrapped class is serializable. + * + * @return true - if it's serializable<br /> + * false otherwise. + * @throws ClassNotFoundException + * if the wrappedclass isn't found. + */ + public static boolean isSerializable(Clazz wrappedclazz) throws ClassNotFoundException { + return wrappedclazz.isSerializable(); + } + + /** + * Prints the usage information. + */ + public static void usage() { + System.out.println("use: serialver [-classpath classpath] [-show] [classname...]"); + } + + /** + * Prints the serialversionUID of a given class. + * + * @param clazz + * the class + * @param suid + * the value to be printed + */ + public static void printResult(Clazz clazz, long suid) { + System.out.println(clazz.getName() + "\t" + "static final long serialVersionUID = " + suid + "L"); + } + + /** + * Get the options from the Map, string options (-classpath <i>path</i>). + * + * @param options + * map. + * @param name + * option. + * @return value of the option. + */ + private static String getString(Map options, String name) { + try { + return (String) options.get(name); + } catch (ClassCastException e) { + throw new RuntimeException("'" + name + "': expected java.lang.String", e); + } + } + + /** + * Get the options from the Map, boolean options (<i>-show</i>). + * + * @param options + * map. + * @param name + * option. + * @return true if option was passed as arg<br /> + * false otherwise. + */ + private static boolean getBoolean(Map options, String name) { + try { + Object value = options.get(name); + if (value != null) { + return ((Boolean) value).booleanValue(); + } + } catch (ClassCastException e) { + throw new RuntimeException("'" + name + "': expected java.lang.Boolean", e); + } + return false; + } + +} Index: src/test/java/org/apache/harmony/tests/tool/serialver/NoSerialize.java =================================================================== --- src/test/java/org/apache/harmony/tests/tool/serialver/NoSerialize.java (revision 0) +++ src/test/java/org/apache/harmony/tests/tool/serialver/NoSerialize.java (revision 0) @@ -0,0 +1,8 @@ +package org.apache.harmony.tests.tool.serialver; + +public class NoSerialize { + private int x; + private int y; + + public String lol; +} Index: src/test/java/org/apache/harmony/tests/tool/serialver/Point.java =================================================================== --- src/test/java/org/apache/harmony/tests/tool/serialver/Point.java (revision 0) +++ src/test/java/org/apache/harmony/tests/tool/serialver/Point.java (revision 0) @@ -0,0 +1,21 @@ +package org.apache.harmony.tests.tool.serialver; + +import java.io.Serializable; + +public class Point implements Serializable { + + /** + * + */ + //private static final long serialVersionUID = -5875064386644620174L; + private int x; + private int y; + private Point p; + + public Point(int x, int y) { + } + + public void setP(Point p) { + this.p = p; + } +} Index: src/test/java/org/apache/harmony/tests/tool/serialver/Test.java =================================================================== --- src/test/java/org/apache/harmony/tests/tool/serialver/Test.java (revision 0) +++ src/test/java/org/apache/harmony/tests/tool/serialver/Test.java (revision 0) @@ -0,0 +1,37 @@ +package org.apache.harmony.tests.tool.serialver; + +import org.apache.harmony.tools.ClassProvider; +import org.apache.harmony.tools.serialver.Clazz; +import org.apache.harmony.tools.serialver.Main; + +import junit.framework.*; + +public class Test extends TestCase { + + public void testCalculeSUID() throws ClassNotFoundException { + ClassProvider classProvider = new ClassProvider(null, null, false); + + Clazz clazz = new Clazz(classProvider, "org.apache.harmony.tests.tool.serialver.Point"); + assertEquals("7980190234566781495", "" + Main.calculeSUID(clazz)); + + clazz = new Clazz(classProvider, "java.lang.String"); + assertEquals("-6849794470754667710", "" + Main.calculeSUID(clazz)); + + clazz = new Clazz(classProvider, "java.util.Random"); + assertEquals("3905348978240129619", "" + Main.calculeSUID(clazz)); + } + + public void testIsSerializable() throws ClassNotFoundException { + ClassProvider classProvider = new ClassProvider(null, null, false); + + Clazz clazz = new Clazz(classProvider, "org.apache.harmony.tests.tool.serialver.NoSerialize"); + assertEquals(false, Main.isSerializable(clazz)); + + clazz = new Clazz(classProvider, "java.lang.Thread"); + assertEquals(false, Main.isSerializable(clazz)); + + clazz = new Clazz(classProvider, "java.io.File"); + assertEquals(true, Main.isSerializable(clazz)); + } + +}