Customizing serialization with Externalizable

With serialization, you can save the current state of an object to a stream and restore it back at a later point in time. With typical serialization, the fields are written using a default mechanism that specifies, for example, the order in which the fields are written. With making an object Externalizable and implementing the methods writeExternal and readExternal, you can do the serialization and restoration of the object yourself, including version checks.

The following example shows you how to customize serialization through the readExternal and writeExternal methods. It creates an object of type Test that contains a String and an integer array. It will only serialize the odd indexes of this array.

Main.java:

import java.io.*;
 
public class Main {
   public static void main(String args[]) {
      try {
         Test t = new Test("esus", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
         System.out.println("Serialize this object:");
         System.out.println(t);
 
         // create byte buffer containing serialized version of t 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);
         t.writeExternal(oos);
         // very important to flush the stream, or you'll get java.io.EOFException
         oos.flush();            
         byte[] buffer = baos.toByteArray(); 
 
         Test t2 = new Test();
         ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(buffer));
         t2.readExternal(ois);      
      
         System.out.println("nDeserialization result:"); 
         System.out.println(t2);
      }
      catch(ClassNotFoundException e) { 
         System.out.println(e);
      }
      catch(IOException e) { 
         System.out.println(e);
      }
   }
}
 
class Test implements Externalizable {
   String s;
   int[] array;
  
   Test() { }
   Test(String s, int[] array) {
      this.s = s;
      this.array = array;
   }
 
   public void writeExternal(ObjectOutput out) throws IOException {
      out.writeObject(s);
      out.writeInt(array.length);
      for (int i=0; i<array.length; i+=2) {
         out.writeInt(i);
         out.writeInt(array[i]);
      }
   }
 
   public void readExternal(ObjectInput in) throws IOException, 
                                                   ClassNotFoundException {
      s = (String) in.readObject();
      int len = in.readInt();
      array = new int[len];
      for (int i=0; i<len/2; i++) {
         int index = in.readInt();
         array[index] = in.readInt();
      }
   }
 
   public String toString() {
      String total = "s = " + s + "; array = ";
      for (int i=0; i<array.length; i++) {
         total += array[i] + " ";
      }
      return total;
   }
}

outputs:

Serialize this object:
s = esus; array = 1 2 3 4 5 6 7 8 9 10
 
Deserialization result:
s = esus; array = 1 0 3 0 5 0 7 0 9 0 

Tracking the progress of reading from an InputStream

You can use the class ProgressMonitorInputStream as described
on Sun’s page How to Monitor Progress.

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.io.*; 
 
public class Main extends JFrame implements ActionListener {
   JTextField fileTextField;
 
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      }); 
       
      JPanel panel = new JPanel();
      panel.setLayout(new FlowLayout(FlowLayout.LEFT));
      panel.add(new JLabel("File:"));
      fileTextField = new JTextField(20);
      panel.add(fileTextField);
      JButton loadButton = new JButton("Load!");
      panel.add(loadButton);
      loadButton.addActionListener(this);
 
      fileTextField.setText("c:\jdk1.2.2\lib\tools.jar");
 
      getContentPane().setLayout(new BorderLayout());
      getContentPane().add(BorderLayout.NORTH, panel);
      pack();
   } 
 
   public void actionPerformed(ActionEvent ae) {
      FileLoader worker = new FileLoader(this, fileTextField.getText());
      worker.start();
   }
 
   class FileLoader extends Thread {
      Component parentComponent;
      String fileName; 
      ProgressMonitor progressMonitor;
 
      public FileLoader(Component parentComponent, String fileName) {
         this.parentComponent = parentComponent;
         this.fileName = fileName;
      }
 
      public void run() {
         try {
            ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(
                                                parentComponent,
                                                "Reading " + fileName,
                                                new FileInputStream(fileName));
            progressMonitor = pmis.getProgressMonitor();
            BufferedInputStream bis = new BufferedInputStream(pmis);
            
            byte b[] = new byte[1024];
            int bytes = bis.read(b, 0, b.length);
            while (bytes > 0) {
               try {
                  Thread.sleep(10);
               }
               catch(InterruptedException e) { } 
               bytes = bis.read(b, 0, b.length);
            }
 
            System.out.println("Loaded!");
         }
         catch(InterruptedIOException e) {
            if (progressMonitor.isCanceled()) {
               System.out.println("Loading of " + fileName + " has been cancelled!");
            }
            else {
               System.out.println(e);
            }
         }
         catch(IOException e) {
            System.out.println(e);
         }
      }
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setVisible(true);
      main.setSize(350, 150);
   }
}

Using PipedInputStream and PipedOutputStream

A PipedInputStream and PipedOutputStream are used to communicate between threads. If you connect the two together, whatever you write to the PipedOutputStream, you receive on the PipedInputStream.

Here’s an example:

Main.java:

import java.io.*;
 
public class Main
{
   public static void main(String []args) {
      try {
         PipedOutputStream pos = new PipedOutputStream();
         PipedInputStream pis = new PipedInputStream(pos);
 
         BufferedInputStream bis = new BufferedInputStream(pis);
  
         new DataCollector(pos).start();
  
         // thread has started to produce data, start 
         // reading data through the pipe here
         while (true) {
            byte[] buffer = new byte[64];
            bis.read(buffer);
            System.out.println("Received: " + new String(buffer));
         }
      }
      catch(IOException e) { 
         e.printStackTrace();
      }
   }
}
 
class DataCollector extends Thread
{
   private BufferedOutputStream bos;
 
   public DataCollector(OutputStream out) {
      this.bos = new BufferedOutputStream(out);
   }
  
   public void run() {
      // generate data here
      int count=0;
      while (true) {
         try {
            count++;
            String data = "data [" + count + "]";
            bos.write(data.getBytes());
            // perform a flush here to make the receiver get it right away
            bos.flush();
            sleep(1000);
         }
         catch(Exception e) {
            e.printStackTrace();
         }
      }
   }
}

outputs:

Received: data [1]
Received: data [2]
Received: data [3]
Received: data [4]
Received: data [5]
Received: data [6]
Received: data [7]
Received: data [8]
Received: data [9]
. . .

Converting an OutputStream to a Writer

An OutputStream is byte oriented. A Writer is Character oriented. To hook up an OutputStream to a Writer and have the conversion done automatically for you, use the class OutputStreamWriter.

Main.java:

import java.io.*;
 
public class Main {   
   public static void main(String[] args) throws Exception {
      String s = "ABC";
 
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      OutputStreamWriter osw = new OutputStreamWriter(baos);
      for (int i=0; i<s.length(); i++) {
         osw.write((int) s.charAt(i));
      }
      osw.close();
 
      byte[] b = baos.toByteArray();
      for (int i=0; i<b.length; i++) {
         System.out.println(b[i]);
      }
   }
}

outputs:

65
66
67

Converting an InputStream to a Reader

An InputStream is byte oriented. A Reader is Character oriented. To hook up an InputStream to a Reader and have the conversion done automatically for you, use the class InputStreamReader.

Main.java:

import java.io.*;
 
public class Main {   
   public static void main(String[] args) throws Exception {
      byte[] b = new byte[] { 65, 66, 67 };
 
      ByteArrayInputStream bais = new ByteArrayInputStream(b);
      InputStreamReader isr = new InputStreamReader(bais);
      int k;
      while ((k = isr.read()) > -1) {
         System.out.println((char) k);
      }
   }
}

outputs:

A
B
C

Creating an OutputStream that manipulates the data written to it

Create a class that extends from FilterOutputStream and override the method write in which you convert the original value to a new value.

Following example introduces the ChangeCaseOutputStream which will convert any character to uppercase or lowercase depending on how you instantiated the class.

Main.java:

import java.io.*;
 
public class Main {
   public static void main(String args[]) {
      try {
         FileOutputStream fos = new FileOutputStream("test.out");
         ChangeCaseOutputStream ccos = new ChangeCaseOutputStream(
                                        ChangeCaseOutputStream.UPPERCASE, fos);
         ccos.write("hello, world!".getBytes());
         ccos.flush();
         ccos.close();
      }
      catch(FileNotFoundException e) {
         System.out.println(e);
      }
      catch(IOException e) {
         System.out.println(e);
      }
   }
}
 
class ChangeCaseOutputStream extends FilterOutputStream {
   public static final int LOWERCASE = 1;
   public static final int UPPERCASE = 2;
 
   private int conversionType;
 
   public ChangeCaseOutputStream(int conversionType, OutputStream in) {
      super(in);
 
      this.conversionType = conversionType;
   }
 
   public void write(int b) throws IOException {
      if (conversionType == LOWERCASE) {
         out.write((int) Character.toLowerCase((char) b));
      }
      else if (conversionType == UPPERCASE) {
         out.write((int) Character.toUpperCase((char) b));
      }
   }
}

Create an InputStream that returns a filtered version of the original InputStream

Create a class that extends from FilterInputStream and override the method read in which you convert the original value to a new value.

Following example introduces the ChangeCaseInputStream which will convert any character to uppercase or lowercase depending on how you instantiated the class.

Main.java:

import java.io.*;
 
public class Main {
   public static void main(String args[]) {
      if (args.length != 1) {
         System.out.println("Usage: java Main <filename.txt>");
         System.exit(1);
      }
 
      try {
         FileInputStream fis = new FileInputStream(args[0]);
         ChangeCaseInputStream ccis = new ChangeCaseInputStream(
                                        ChangeCaseInputStream.UPPERCASE, fis);
         int b;
         while ((b = ccis.read()) > 0) {
            System.out.print((char) b);
         }
         ccis.close();
      }
      catch(FileNotFoundException e) {
         System.out.println(e);
      }
      catch(IOException e) {
         System.out.println(e);
      }
   }
}
 
class ChangeCaseInputStream extends FilterInputStream {
   public static final int LOWERCASE = 1;
   public static final int UPPERCASE = 2;
 
   private int conversionType;
 
   public ChangeCaseInputStream(int conversionType, InputStream in) {
      super(in);
 
      this.conversionType = conversionType;
   }
 
   public int read() throws IOException {
      int b = in.read();
 
      // first check for EOF
      if (b < 0) {
         return b;
      }
 
      if (conversionType == LOWERCASE) {
         b = (int) Character.toLowerCase((char) b);
      }
      else if (conversionType == UPPERCASE) {
         b = (int) Character.toUpperCase((char) b);
      }
 
      return b;     
   }
}

Subtracting days from a date

The following example subtracts 200 days from the current date.

Main.java:

import java.util.*;
 
public class Main
{
   public static void main(String []args) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(new Date());
      int year  = cal.get(Calendar.YEAR);
      int month = cal.get(Calendar.MONTH);
      int day   = cal.get(Calendar.DATE);
      month++;   // months are from 0-11, so add one!
 
      printDate(cal);
 
      cal.add(Calendar.DATE, -200);
 
      printDate(cal);
   }
 
   public static void printDate(Calendar cal) {
      System.out.println((cal.get(Calendar.MONTH)+1) + "-" +
                         cal.get(Calendar.DATE) + "-" +
                         cal.get(Calendar.YEAR));
   }
}

outputs:

4-28-2002
10-10-2001

Combining multiple InputStreams

You can combine two or more inputstreams with the class SequenceInputStream. It allows
you to put them next to each other, in sequence:

           SequenceInputStream
           +-----------------------+
           |  +-----+-----+-----+  |
 read() <--+  | IS1 | IS2 | IS3 |  |
           |  +-----+-----+-----+  |
           +-----------------------+

There are two ways to construct a SequenceInputStream through its two constructors. You could either pass an enumeration of all your inputstreams, or you can pass in two inputstreams.
The following example allows you to specify two filenames which will be printed out on the console.

Main.java:

import java.util.*;
import java.io.*;
  
public class Main {   
   public static void main(String[] args) throws Exception {
      if (args.length != 2) {
         System.out.println("Usage: java Main <filename1> <filename2>");
         System.exit(1);
      }
 
      FileInputStream fis1 = new FileInputStream(args[0]);
      FileInputStream fis2 = new FileInputStream(args[1]);
 
      SequenceInputStream sis = new SequenceInputStream(fis1, fis2);
      int k;
      while ((k = sis.read()) > -1) {
         System.out.print((char) k);
      }
   }
}

(try it out with two small textfiles :)

Skipping part of an InputStream when reading from it

You can do so using the method skip. You can call this method from any point where you are in the inputstream.

The following program skips half of the file, where the filename is supplied at command line.

Main.java:

import java.io.*;
  
public class Main {
   public static void main(String args[]) throws Exception {
      if (args.length != 1) {
         System.err.println("Usage: java Main <file.txt>");
         System.exit(1);
      }
 
      File file = new File(args[0]);
      BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
 
      int half = (int) file.length() / 2;
      bis.skip(half);
 
      int b;
      while ((b = bis.read()) > 0) {
         System.out.print((char) b);
      } 
   }
}