/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
/**
* A simple class that implements a growable array of ints, and knows how to
* serialize itself as efficiently as a non-growable array.
*/
public class SerialIntList implements Serializable {
// These are the fields of this class. By default the serialization
// mechanism would just write them out. But we've declared size to be
// transient, which means it will not be serialized. And we've
// provided writeObject() and readObject() methods below to customize
// the serialization process.
protected int[] data = new int[8]; // An array to store the numbers.
protected transient int size = 0; // Index of next unused element of array
/** Return an element of the array */
public int get(int index) {
if (index >= size)
throw new ArrayIndexOutOfBoundsException(index);
else
return data[index];
}
/** Add an int to the array, growing the array if necessary */
public void add(int x) {
if (data.length == size)
resize(data.length * 2); // Grow array if needed.
data[size++] = x; // Store the int in it.
}
/** An internal method to change the allocated size of the array */
protected void resize(int newsize) {
int[] newdata = new int[newsize]; // Create a new array
System.arraycopy(data, 0, newdata, 0, size); // Copy array elements.
data = newdata; // Replace old array
}
/**
* Get rid of unused array elements before serializing the array. This may
* reduce the number of array elements to serialize. It also makes data.length ==
* size, so there is no need to safe the (transient) size field. The
* serialization mechanism will automatically call this method when
* serializing an object of this class. Note that this must be declared
* private.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
if (data.length > size)
resize(size); // Compact the array.
out.defaultWriteObject(); // Then write it out normally.
}
/**
* Restore the transient size field after deserializing the array. The
* serialization mechanism automatically calls this method.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Read the array normally.
size = data.length; // Restore the transient field.
}
/**
* Does this object contain the same values as the object o? We override this
* Object method so we can test the class.
*/
public boolean equals(Object o) {
if (!(o instanceof SerialIntList))
return false;
SerialIntList that = (SerialIntList) o;
if (this.size != that.size)
return false;
for (int i = 0; i < this.size; i++)
if (this.data[i] != that.data[i])
return false;
return true;
}
/** We must override this method when we override equals(). */
public int hashCode() {
int code = 1; // non-zero to hash [0] and [] to distinct values
for (int i = 0; i < size; i++)
code = code * 997 + data[i]; // ignore overflow
return code;
}
/** A main() method to prove that it works */
public static void main(String[] args) throws Exception {
SerialIntList list = new SerialIntList();
for (int i = 0; i < 100; i++)
list.add((int) (Math.random() * 40000));
SerialIntList copy = (SerialIntList) Serializer.deepclone(list);
if (list.equals(copy))
System.out.println("equal copies");
Serializer.store(list, new File("intlist.ser"));
}
}
class Serializer {
/**
* Serialize the object o (and any Serializable objects it refers to) and
* store its serialized state in File f.
*/
static void store(Serializable o, File f) throws IOException {
ObjectOutputStream out = // The class for serialization
new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(o); // This method serializes an object graph
out.close();
}
/**
* Deserialize the contents of File f and return the resulting object
*/
static Object load(File f) throws IOException, ClassNotFoundException {
ObjectInputStream in = // The class for de-serialization
new ObjectInputStream(new FileInputStream(f));
return in.readObject(); // This method deserializes an object graph
}
/**
* Use object serialization to make a "deep clone" of the object o. This
* method serializes o and all objects it refers to, and then deserializes
* that graph of objects, which means that everything is copied. This differs
* from the clone() method of an object which is usually implemented to
* produce a "shallow" clone that copies references to other objects, instead
* of copying all referenced objects.
*/
static Object deepclone(final Serializable o) throws IOException, ClassNotFoundException {
// Create a connected pair of "piped" streams.
// We'll write bytes to one, and them from the other one.
final PipedOutputStream pipeout = new PipedOutputStream();
PipedInputStream pipein = new PipedInputStream(pipeout);
// Now define an independent thread to serialize the object and write
// its bytes to the PipedOutputStream
Thread writer = new Thread() {
public void run() {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(pipeout);
out.writeObject(o);
} catch (IOException e) {
} finally {
try {
out.close();
} catch (Exception e) {
}
}
}
};
writer.start(); // Make the thread start serializing and writing
// Meanwhile, in this thread, read and deserialize from the piped
// input stream. The resulting object is a deep clone of the original.
ObjectInputStream in = new ObjectInputStream(pipein);
return in.readObject();
}
/**
* This is a simple serializable data structure that we use below for testing
* the methods above
*/
public static class DataStructure implements Serializable {
String message;
int[] data;
DataStructure other;
public String toString() {
String s = message;
for (int i = 0; i < data.length; i++)
s += " " + data[i];
if (other != null)
s += "\n\t" + other.toString();
return s;
}
}
/** This class defines a main() method for testing */
public static class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// Create a simple object graph
DataStructure ds = new DataStructure();
ds.message = "hello world";
ds.data = new int[] { 1, 2, 3, 4 };
ds.other = new DataStructure();
ds.other.message = "nested structure";
ds.other.data = new int[] { 9, 8, 7 };
// Display the original object graph
System.out.println("Original data structure: " + ds);
// Output it to a file
File f = new File("datastructure.ser");
System.out.println("Storing to a file...");
Serializer.store(ds, f);
// Read it back from the file, and display it again
ds = (DataStructure) Serializer.load(f);
System.out.println("Read from the file: " + ds);
// Create a deep clone and display that. After making the copy
// modify the original to prove that the clone is "deep".
DataStructure ds2 = (DataStructure) Serializer.deepclone(ds);
ds.other.message = null;
ds.other.data = null; // Change original
System.out.println("Deep clone: " + ds2);
}
}
}