Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Java Tip 36: Share Java objects using e-mail

The Serializable interface, new with JDK 1.1, simplifies object persistence. Here's how to transfer an object to another user via SMTP e-mail

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 3 of 6

Now here's how we could reconstitute a copy of the object later:

 1   import java.io.*;
    :
 2   FileInputStream fis = new FileInputSteam("MyBug.test");
 3   BugReport bug = new BugReport().restore(fis);


Even easier! Isn't Java great...and getting better!

Now we'll modify line 3 in the second example a little so that it causes the object to be written to an array of bytes instead of a file:

 
 1   import java.io.*;
     :
 2   BugReport bug = new BugReport(1.0, "Crashes when spell checker invoked", 2);
 3   ByteArrayOutputStream os = new ByteArrayOutputStream();
 4   bug.save(os);


So that's it. We have created an object and learned how to serialize it into a ByteOutputStream. Next, we will take this ByteOutputStream and convert it to a String of Base64-encoded characters.

Base64 encoding

The current standard for Internet e-mail is set forth in RFC 821, Simple Mail Transport Protocol (SMTP). For our purposes, RFC 821 imposes two important but not too onerous restrictions on the content of mail messages:

  1. Mail messages must be composed of 7-bit US-ASCII characters.

  2. Lines in mail messages must not be longer than 1,000 characters.


Our memory-resident serialized object must, therefore, be converted to some other format in order to be e-mailed via SMTP.

RFC 1521 provides a possible solution. It defines mail message bodies such that they can contain multiple kinds of data. This standard is more commonly known as multipart MIME.

According to RFC 1521:

The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single digit in the Base64 alphabet.


This means that if we had the following 3-byte bit pattern of arbitrary binary data as input -- xC, xF3, xFF -- it would be Base64-encoded as x3, xF, xF, x3F, as illustrated below:


Base64 encoding example


The description of Base64 encoding may sound a little arcane, but the code to implement it is quite simple, as illustrated in the next code example. In that example, I have created a new class, Codecs. For now, the Codecs class has two methods: one for encoding an array of bytes, and one for encoding a String. The String-encoder simply calls the getBytes() method of the String class and then encodes the resulting array of bytes. Later, we will add methods for decoding from Base64 to the original format.

 
 1  public class Codecs {
 2  private Codecs() {}  // do not instantiate this class

3 public final static String base64Encode(String strInput) { 4 if (strInput == null) return null;

5 byte byteData[] = new byte[strInput.length()]; 6 strInput.getBytes(0, strInput.length(), byteData, 0); 7 return new String(base64Encode(byteData), 0); 8 }
9 public final static byte[] base64Encode(byte[] byteData) { 10 if (byteData == null) return null; 11 int iSrcIdx; // index into source (byteData) 12 int iDestIdx; // index into destination (byteDest) 13 byte byteDest[] = new byte[((byteData.length+2)/3)*4];
14 for (iSrcIdx=0, iDestIdx=0; iSrcIdx < byteData.length-2; iSrcIdx += 3) { 15 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx] >>> 2) & 077); 16 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx+1] >>> 4) & 017 | (byteData[iSrcIdx] << 4) & 077); 17 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx+2] >>> 6) & 003 | (byteData[iSrcIdx+1] << 2) & 077); 18 byteDest[iDestIdx++] = (byte) (byteData[iSrcIdx+2] & 077); 19 }
20 if (iSrcIdx < byteData.length) { 21 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx] >>> 2) & 077); 22 if (iSrcIdx < byteData.length-1) { 23 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx+1] >>> 4) & 017 | (byteData[iSrcIdx] << 4) & 077); 24 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx+1] << 2) & 077); 25 } 26 else 27 byteDest[iDestIdx++] = (byte) ((byteData[iSrcIdx] << 4) & 077); 28 }
29 for (iSrcIdx = 0; iSrcIdx < iDestIdx; iSrcIdx++) { 30 if (byteDest[iSrcIdx] < 26) byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + 'A'); 31 else if (byteDest[iSrcIdx] < 52) byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + 'a'-26); 32 else if (byteDest[iSrcIdx] < 62) byteDest[iSrcIdx] = (byte)(byteDest[iSrcIdx] + '0'-52); 33 else if (byteDest[iSrcIdx] < 63) byteDest[iSrcIdx] = '+'; 34 else byteDest[iSrcIdx] = '/'; 35 }
36 for ( ; iSrcIdx < byteDest.length; iSrcIdx++) 37 byteDest[iSrcIdx] = '=';
38 return byteDest; 39 } 40 }


Line Description


1-2 Declares the class public and defines a constructor that is not meant to be called by user-written code. Generally, this class should not be instantiated.


3-8 Defines an encodeBase64() method that returns a Base64-encoded version of the String passed in as an argument. It accomplishes this by calling the String.getBytes() method and passing the resulting array of bytes to encodeBase64(byte[]).


9-39 Defines an encodeBase64() method that returns a Base64-encoded array of bytes based on the array of bytes passed in as an argument.


10 If we received a null argument, exit this method.


11-13 Declare working variables including an array of bytes that will contain the encoded data to be returned to the caller. Note that the encoded array is about 1/3 larger than the input. This is because every group of 3 bytes is being encoded into 4 bytes.


14-19 Walk through the input array, 24 bits at a time, converting them from 3 groups of 8 to 4 groups of 6 with two unset bits between. This code is simpler than it first appears. Study it carefully, comparing it to the illustration above, until you understand what is going on.


20-28 If the number of bytes we received in the input array was not an even multiple of 3, convert the remaining 1 or 2 bytes.


29-35 Use the encoded data as indexes into the Base64 alphabet. (The Base64 alphabet is completely documented in RFC 1521.)


36-37 Pad any unused bytes in the destination string with '=' characters.


38 Return Base64-encoded bytes to the caller.


We're making good progress. So far we have serialized the object into a memory-resident object and converted the serialization data to Base64-encoding so that it is ready to be e-mailed to its destination user(s). To summarize what we have accomplished so far, here is a code fragment that creates an instance of the BugReport object, serializes it into memory, and Base64-encodes it:

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources