Serial version Tasks User Manual

Overview

Serialization of objects to persistent storage presents a potential problem with class version mismatches. This is because the class itself is not serialized with the object. Instead, an instance of java.io.ObjectStreamClass that describes the class of the object is serialized. The java.io.ObjectStreamClass contains the class name and a version identifier for the serialized object. This allows the local version of the class to evolve independently of the serialized data.

The compatible class modifications, such as to add a method, modify the signature of the class. If we try to read a serialized object with a class that changed like this, the JVM returns a java.io.InvalidClassException (Local class not compatible) exception.

To tell to the JVM that nothing significant changed since the last compatible class, we have to mark the class with the same stream identification, named SUID. The JDK provides the serialver program that gives the SUID for all serializable class.

The static member variable called serialVersionUID can be included in the code of the new class version that implements a compatible modification. So, all objects serialized by the original class are compatible with the new class.

Each versioned class must identify the original class version for which it is capable of writing streams and from which it can read. For example, a versioned class must declare:

private static final long serialVersionUID = 3487495895819393L;

For more information about the versioning, see the Sun documentation, and the java.io.ObjectStreamClass.getSerialVersionUID() method.

The net.sourceforge.serialver package consists of a framework to support the serialver functionality:
serialver Gets the SUID of serializable classes.
getclassname FilterReader to get the full classname from a source file.
commentsuid FilterReader to comment out the SUID declaration.
replacesuid FilterReader to replace the SUID value.
Example Shows a project to use these functionalities together.


SerialVer Task

Description

Gets the serialVersionUID of serializable classes, in a form suitable for copying into evolving classes.

This task wraps the serialver executable provided in the JDK and echoes the result. The result can also be saved in a project property to be used later, such as in a FilterReader. The content is like a property file, where the key is the classname and the value is the SUID value. If the project property name doesn't exist, it will be created, otherwise the content will be replaced.

Parameters

Attribute Description Required
serializableclass Comma separated list of serializable classnames. Yes
classpath Specify where to find user class files. Default: CLASSPATH of the system. No
failonerror Stop the build process if the command exits with a return code other than 0. Default: yes. No
failifnotserializable Stop the build process if any of the classes specified for ammendment are not Serializable Default: yes. No
property Project property to store the serialver result. No

Parameters specified as nested elements

classpath

The classpath attribute is a PATH like structure and can also be set via nested classpath element.

Note: The classpath argument has no effect under the JDK1.1 because this argument is not supported in the serialver executable.

serializableclass

Same as one entry in the list given by serializableclass.

Parameters
Attribute Description Required
serializableclass The serializable classname Yes

Example

The following example echoes a message with the result of the serialver executable and save it in the project property named result.properties. The serialver executable treats the TestSerialverA and com.mypackage.TestSerialverB classes, compiled in the "./classes" path.

  <taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/>
  <serialver classpath="./classes"
             serializableclass="TestSerialverA, com.mypackage.TestSerialverB"
             property="result.properties"/>
Or:
  <taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/>
  <serialver property="result.properties">
      <classpath path="./classes"/>
      <serializableclass name="TestSerialverA"/>
      <serializableclass name="com.mypackage.TestSerialverB"/>
  </serialver>

The content of the message looks like:

TestSerialverA:    static final long serialVersionUID = 8266471551516979661L;
com.mypackage.TestSerialverB:    static final long serialVersionUID = 1434592923469599839L;

The content of the result.properties property looks like:

#serialver
#Mon Jul 08 15:26:45 PDT 2002
com.mypackage.TestSerialverB=1434592923469599839L
TestSerialverA=8266471551516979661L

getclassname FilterReader

Description

This filter parses a source code to get the classname. The classnames are returned via a project property that contains a comma-separated list. This filter creates the project property if it doesn't exist, otherwise merges the content.

Parameters

Parameter Description Required
property Name of a project property to store the classname of the source file. No

Example

This copies Java source files from the ${dir.src} directory to the ${dir.dest} directory. A message lists the classnames of these files in a coma-separated format.

    <copy todir="${dir.dest}">
        <fileset dir="${dir.src}">
            <include name="**/*.java"/>
        </fileset>
        <filterchain>
            <filterreader classname="net.sourceforge.serialver.GetClassname">
                <param name="property" value="value.classnames"/>
            </filterreader>
        </filterchain>
    </copy>
    <echo message="The classnames are: ${value.classnames}"/>

The content of the message looks like:

The classnames are: TestSerialverA, com.mypackage.TestSerialverB

Rule


commentsuid FilterReader

Description

This filter comments out the serial version declaration (SUID) in Java source file.

Example

This copies Java source files from the ${dir.src} directory to the ${dir.dest} directory. During the copy, the SUID declarations are put in comment. If it was already in comment, the line is untouched.

    <copy todir="${dir.dest}">
        <fileset dir="${dir.src}">
            <include name="**/*.java"/>
        </fileset>
        <filterchain>
            <filterreader classname="net.sourceforge.serialver.CommentSUID"/>
        </filterchain>
    </copy>

For a source file like:

    public class TestSerialverA implements Serializable {
        private static final long serialVersionUID = 8266471551516979661L; // SUID
        ...

The result looks like:

    public class TestSerialverA implements Serializable {
    //AUTO_SUID    private static final long serialVersionUID = 8266471551516979661L; // SUID
        ...

Rule


replacesuid FilterReader

Description

This filter parses the source code to update the serial version UID declaration. If the SUID declaration was in comment, the line comment is removed. The filter takes parameters composed of the format "classname=suidValue". The parameter can be the content of a property file.

Parameters

Parameter Description Required
suidproperties A property or a list of properties that contains the SUID values for the classnames. No
replaceAny A property that if set to Yes means that SUIDs already in comment i.e. // will not be overwritten with a new calculated value if different (default = No) No

Example

This copies Java source files from the ${dir.src} directory to the ${dir.dest} directory. During the copy, the SUID is updated and the line comment of the SUID declaration is removed. The ${suid.file.values} property file and the FilterReader parameter provide the new SUID values.

    <loadfile srcfile="${suid.file.values}" property="suid.property.values"/>
    <copy todir="${dir.dest}">
        <fileset dir="${dir.src}">
            <include name="**/*.java"/>
        </fileset>
        <filterchain>
            <filterreader classname="net.sourceforge.serialver.ReplaceSUID">
                <param name="replaceAny" value="Yes"/>
                <param name="suidproperties" value="com.mypackage.TestSerialverB=1434592923469599839L"/>
                <param name="suidproperties" value="${suid.property.values}"/>
            </filterreader>
        </filterchain>
    </copy>

For a source file like:

    package  com.mypackage;
    public class TestSerialverB implements Serializable {
    //    static final long serialVersionUID = 0L; // SUID
        ...

The result looks like:

    package  com.mypackage;
    public class TestSerialverB implements Serializable {
    //    static final long serialVersionUID = 1434592923469599839L; // SUID
        ...

Rule


Example

This example shows how to work with the serialver task and associated FilterReaders. In summary, the project has a task to stop the compatibility of the serialized classes, and has a second task to create a new version of compatible classes. A simple scenario could be like:

To avoid this incompatibly, when the developer just splits a method for instance, Java provides a way to force the signature of the compiled class by inserting a special declaration in the source code. An executable is provided in the JDK to give this declaration from a compiled class. ANT can helps the developer on this point, and also when he needs to create a new version of compatible classes. In this case, he has to remove the SUID declaration from the source code, compile, run the executable and insert the new declaration in the source code. The next compilations (and serialized objects) will be compatible with this version, but not anymore with the first one.

In this example, the developer can add in the "demo.src.suid" FileSet the TestSerialver.java file and run the "Replace SUID" target before to do a compatible modification in the source code. It is also a good practice to do it once the serializable class is stable. All the next compilations and serializations will be compatible because this task has inserted a SUID declaration in the source code of TestSerialver.java.

When a big modification is planed on the TestSerialver.java file that will break the compatibilities of the classes, the developer can run the "Comment SUID" target. All the next compilation will have a different signature than the serialized objects of the first version, because the task has put in comment the SUID declaration. When the modifications in TestSerialver.java are stable, the developer can create a new version of compatible classes by running the "Replace SUID" target.

The tasks are:

    <taskdef name="serialver" classname="net.sourceforge.serialver.SerialVer"/>

    <property name="demo.dir.src" value="${dir.work}"/>
    <property name="demo.dir.dest" value="${dir.work}"/>
    <property name="demo.dir.bak" value="${dir.work}/backup"/>
    <property name="demo.dir.tmp" value="${dir.work}/tmp"/>
    <property name="demo.suid.file" value="${demo.dir.dest}/suid.properties"/>
    <property name="demo.suid.values" value=""/>

    <path id="demo.classpath">
        <pathelement location="${demo.dir.tmp}"/>
        <!-- pathelement location="Project Classpath"/ -->
    </path>

    <fileset dir="${demo.dir.src}" id="demo.src.suid">
        <include name="**/TestSerialver*.java"/>
        <!-- include name="Serializable Classes"/ -->
    </fileset>

    <!-- Task: Comment out the SUID declaration. -->
    <target name="Comment SUID"
            description="Comment out the SUID in the src files">
        <delete dir="${demo.dir.tmp}"
                includeEmptyDirs="yes"
                failonerror="false" />
        <mkdir  dir="${demo.dir.dest}"/>
        <mkdir  dir="${demo.dir.bak}"/>
        <mkdir  dir="${demo.dir.tmp}"/>

        <!-- Backup src -->
        <copy todir="${demo.dir.bak}" overwrite="yes">
            <fileset refid="demo.src.suid" />
            <mapper type="glob" from="*.java" to="*.java.bak"/>
        </copy>

        <!-- Comment SUIDs -->
        <copy todir="${demo.dir.tmp}">
            <fileset refid="demo.src.suid" />
            <filterchain>
                <filterreader classname="net.sourceforge.serialver.GetClassname">
                    <param name="property" value="demo.suid.values"/>
                </filterreader>
                <filterreader classname="net.sourceforge.serialver.CommentSUID"/>
            </filterchain>
        </copy>

        <!-- Replace scr files -->
        <copy todir="${demo.dir.src}" overwrite="yes">
            <fileset dir="${demo.dir.tmp}" >
                <include name="**/*.java"/>
            </fileset>
        </copy>
    </target>

    <!-- Task: Replace SUID values. -->
    <target name="Replace SUID"
            description="Replace the SUID in the src files"
            depends="Comment SUID" >
        <!-- get SUIDs -->
        <javac   srcdir="${demo.dir.tmp}"
                 destdir="${demo.dir.tmp}"
                 debug="on" >
            <classpath refid="demo.classpath"/>
        </javac>
        <echo message="Serializable classes: ${demo.suid.values}" level="verbose" />
        <serialver serializableclass="${demo.suid.values}"
                   property="demo.suid.values">
            <classpath refid="demo.classpath"/>
        </serialver>
        <echo message="SUIDs: ${demo.suid.values}" level="verbose" />

        <!-- replace SUIDs -->
        <copy todir="${demo.dir.src}" overwrite="yes">
            <fileset dir="${demo.dir.tmp}" >
                <include name="**/*.java"/>
            </fileset>
            <filterchain>
                <filterreader classname="net.sourceforge.serialver.ReplaceSUID">
                    <param name="suidproperties" value="${demo.suid.values}"/>
                </filterreader>
            </filterchain>
        </copy>

        <!-- Save SUIDs -->
        <echo message="${demo.suid.values}" file="${demo.suid.file}"/>
        <echo message="${demo.suid.file}: ${demo.suid.values}" level="verbose"/>
    </target>

By Stéphane Chauvin (stchauvin@users.sourceforge.net)

Copyright © 2002 Apache Software Foundation. All rights Reserved.