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.
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:
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: