HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Last month's issue (JDJ, Vol. 3, Issue 12) covered the basic concepts of programming with Java's I/O streams, such as the difference between byte and character streams, the various stream classes, the concept of stream chaining and more. We'll conclude the subject this month by looking at some practical uses of these streams.

There are so many uses of streams in Java apps that it's almost impossible to imagine all the ways they can be applied. Nevertheless, I'm going to show you several miniature programs that demonstrate some of the ways that Java API streams can be used. We'll also look at a sample (yet robust) application that uses a mixed bag of these classes.

File Input/Output
File programming in Java is easy -- there's a class to open a file for reading and another for writing to an output file. Listing 1 demonstrates a simple copy utility (similar to the MS-DOS "copy" and Unix "cp" commands). The code in Listing 2 initially opens the input and output files, then uses the following code to copy the input file to the output file and keeps a counter for the number of bytes copied:

while ((read = fr.read(c)) != -1)
{
fw.write(c, 0, read);
total += read;
};

Properties
Java provides the java.util.Properties class as a facility for working with configuration files similar to the Windows INI file format that contains unique key=value pairs per line as shown in this example:
user=anil
[email protected]
webPage=http://divya.com/people/anil/ ;

Not surprisingly, the Properties class provides methods for reading from and writing to configuration files. Listing 3 demonstrates how to load a file using the Properties.load() method and how to extract parameters from the configuration file using the getProperty() method. To save to a configuration file, simply use the Properties.save() method.

Executing Programs
There may be times when you need to invoke command line programs (e.g., Unix sed or ls), pass data to them and read their output. To do this, you have to use the java.lang.Runtime class to start a program and communicate with it via its input, output and/or error streams. The following lines from Listing 4 run the Unix "ls -l" command and obtain the command's input stream in order to read the program's output:

Process p = Runtime.getRuntime().exec( "ls -l");
InputStream is = p.getInputStream();;

Servlet Programming
If you haven't worked with servlets and are still programming using CGI scripts, you're missing out on some cool stuff. Servlets are the server-side equivalent of applets (applets without the GUI). They also make extensive use of input and output streams (or readers and writers) as demonstrated in Listing 5. The following code fragment from Listing 5 reads all the input sent by the client (most likely an HTML form in a Web browser) using the POST method and echoes it back to the client using the output stream:

InputStream is = req.getInputStream();
OutputStream os = res.getOutputStream();
...
while ((c = is.read(b)) != -1)
os.write(b, 0, c);;

Object Serialization
Object serialization allows a developer to save an object and all its nontransient data to an output stream, and then restore it by reading it back in from an input stream. For example, if we have an "Employee&" object that contains three data members - an employee ID, name and salary - and we wanted to save objects for each employee (e.g., John Smith) to a file, we would use code similar to this:

Employee e = new Employee(1, "John Smith", 55000);
FileOutputStream f = new FileOutputStream("JohnSmith.dat");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject(e);;

To restore the object saved above, we would use code similar to this:

FileInputStream in = new FileInputStream("JohnSmith.dat");
ObjectInputStream s = new ObjectInputStream(in);
Employee e = (Employee)s.readObject();;

Note that the objects that need to be serialized must implement the Serializable interface by using the "implements java.io.Serializable" statement. However, this interface doesn't require any methods to be defined in the implementation class.

Incidentally, RMI (Remote Method Invocation) makes extensive use of object serialization to exchange objects between the RMI client and server.

Working with Streams in Memory
Working with streams in memory is just as easy as working with any other kind of streams. For example, if we modified the program in Listing 1 (Type.java) to work with a java.io.StringWriter instead of a PrintWriter, we could rewrite that code as follows:

StringWriter sw = new StringWriter();
...
while ((read = fr.read(c)) != -1)
sw.write(c, 0, read);
...
System.out.println(sw.toString());

Standard Input, Output and Error Programming
Standard device programming can be accomplished via three static data members of the java.lang.System class: System.in, System.out and System.err. System.out and System.err are of type java.io.PrintStream while System.in is simply a java.io.InputStream. We've already seen a couple of examples of System.out in our listings. Note that, like other classes, these too can be chained to achieve the required goal.

Sample App Using Socket, GZIP, File and JDBC Streams
Now that we've looked at several miniature programs, it's time to examine a more robust application that uses a mixed bag of the stream/writer classes.

Figure 1 provides a global view of the hypothetical application and is intended to demonstrate how a few lines of Java code can be empowered if the proper TCP/IP network is in place.

Figure 1
Figure 1:

A hospital in California and another in Florida connect to their processing center (via sockets) in Virginia so they can relay information about their patients on a daily basis. The protocol used to send the data is simple: the first line contains the hospital name, the second line contains a patient name, and the the remaining lines contain the patient's data (e.g., name, address, medical history).

The complete code for the client-side processing can be seen in Listing 6.

<p align="center"><img
src="../../../~BUSIN~1/~PUBLI~1/IOSTRE~1/figure2.gif" border="1"
width="500" height="380"> </font>
<p align="center"><strong>

The server in the Virginia data center receives the client (California, Florida) data, stores a local copy in a file using GZIP compression, then forwards the same (compressed) data to a medical reporting agency in Europe by using JDBC to store the data in their relational database. Listing 7 shows the complete source code for the server-side processing.

The reporting agency then runs a program (see Listing 8) to view the patient data stored in their relational database.

Note that I haven't explained the programs in Listings 6 through 8, hoping that by now you've learned enough about Java streams to follow the code with the help of my comments in the source code.

Writing Custom Stream Classes
Writing your own stream classes is easy - you simply extend the top-level classes (java.io.Reader/Writer or java.io.Filter*) and implement the required methods. To get an idea of how to write your own classes, take a look at the source code for the various descendant classes in the JDK software. If you're using an IDE instead of Sun's JDK to develop Java programs, look around in the IDE's software directories for the JDK source code (e.g., \VisualCafeDbDe\Java\src).

Why would you write a custom class? Most likely because you want to process the data in a certain way when reading or writing it. Take our backup software, BackOnline, for example, which uses 56-bit DES encryption streams when backing up or restoring data. We had to write it because we wanted to use stream chaining and were missing the encryption stream classes since they didn't exist in JDK 1.0.

Summary
By now you should have a good handle on how I/O streams work. Keep in mind that many new APIs (e.g., Media, 2D/3D, Mail) not covered in this article make use of streams. So take a few minutes to explore the various classes in the java.io package and get a thorough understanding of them - it will be well worth your time if you plan to program in Java.

About the Author
Anil Hemrajani is a senior consultant at Divya Incorporated, a firm specializing in Java/Internet solutions. He provides consulting services to Fortune 500 companies and is a frequent writer and speaker. He can be reached at [email protected]

	

Listing 1:
 
Type.java<import java.io.*;

/** Displays contents of a file (e.g. java Type app.ini) */
public class Type
{
    public static void main(String args[])
                       throws Exception
    {
       // Open input/output and setup variables
       FileReader  fr = new FileReader(args[0]);
       PrintWriter pw = new PrintWriter(System.out, true);
       char c[]       = new char[4096];
       int  read      = 0; 

       // Read (and print) till end of file
       while ((read = fr.read(c)) != -1)
          pw.write(c, 0, read);

       // Close shop
       fr.close();
       pw.close();
    }
}

Listing 2:

Copy.javamport java.io.*;

/** Copies a file (e.g. java Copy config.sys config.bak) */
public class Copy
{
    public static void main(String args[])
                       throws Exception
    {
       if (args.length < 2)
       {
          System.err.println("usage: java Copy InputFile OutputFile");
          System.exit(1);
       }

       // Open input/output and setup variables
       FileReader fr = new FileReader(args[0]);
       FileWriter fw = new FileWriter(args[1]);
       char c[]      = new char[4096];
       int  read     = 0, total = 0;

       // Read (and print) till end of file
       while ((read = fr.read(c)) != -1)
       {
          fw.write(c, 0, read);
          total += read;
       }

       // Close shop
       fr.close();
       fw.close();

       System.out.println(total + " bytes copied");
    }
}

Listing 3: Config.java

import java.io.*;
import java.util.Properties;


/** Displays a config file (e.g. java Config app.ini) */
public class Config
{
    public static void main(String args[])
                       throws Exception
    {
       // Open input/output and setup variables
       FileInputStream fis = new FileInputStream(args[0]);
       Properties p        = new Properties();

       p.load(fis);
       fis.close();

       // Display email id property
       System.out.println("email = " + p.getProperty("email"));
    }

Listing 4: Run.java import java.io.*;


/* Displays output of a program (e.g. java Run "ls -l") */
public class Run
{
    public static void main(String args[])
                       throws Exception
    {
       Process p      = Runtime.getRuntime().exec("ls -l");
       InputStream is = p.getInputStream();
       byte b[]       = new byte[4096];
       int  c;

       while ((c = is.read(b)) != -1)
          System.out.write(b, 0, c);

       p.destroy();
    }
}

Listing 5: ServletTest.java
 
import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;



/** Demo use of streams in Servlets
  * (e.g. http://myhost.com/servlet/ServletTest/
  */
public class ServletTest
             extends HttpServlet
{
    public void doPost(HttpServletRequest req, HttpServletResponse res)
                throws ServletException, IOException
    {
       InputStream  is = req.getInputStream();
       OutputStream os = res.getOutputStream();
       byte b[]        = new byte[4096];
       int  c;

       res.setContentType("text/plain");
       while ((c = is.read(b)) != -1)
          os.write(b, 0, c);
    }


    public void doGet(HttpServletRequest req, HttpServletResponse res)
                throws ServletException, IOException
    {
       res.setContentType("text/html");

       ServletOutputStream sos = res.getOutputStream();
       sos.println("Please use the POST method for this servlet!");
    }

Listing 6: SendData.java

import java.io.*;
import java.net.*;


/** Transmit patient data to Data Center in Virginia
  * (e.g. java SendData remoteHost hospitalName patientDataDir)
  */
public class SendData
{
    private static final String DONE=".done";


    public static void main(String args[])
                       throws Exception
    {
       // Check arguments
       if (args.length < 3)
       {
          System.err.println("usage: java SendData remoteHost hospitalName patientDataDir");
          System.exit(1);
       }

       // Initialize variables
       String remoteHost   = args[0],
              hospitalName = args[1],
              dataDir      = args[2],
              fileList[]   = null,
              patientFile  = null;

       // Ensure that directory exists
       File f = new File(dataDir);
       if (!f.exists() || !f.isDirectory())
       {
           System.err.println(dataDir + " is an invalid directory");
           return;
       }

       // Ensure there are files to process
       fileList = f.list();
       if (fileList == null || fileList.length < 1)
       {
           System.err.println("No files to process");
           return;
       }

       Socket s          = null;
       BufferedWriter bw = null;
       FileReader fr     = null;
       File origFile     = null, renamedFile = null;
       char c[]          = new char[4096];
       int read, patientsProcessed = 0;

       // Process each file
       for (int i=0; i < fileList.length; i++)
       {
          // Do not process previously processed or empty files
          origFile = new File(dataDir, fileList[i]);
          if (fileList[i].endsWith(DONE) || origFile.length() < 1)
          {
              System.out.println("Skipping file " + fileList[i]);
              continue;
          }

          patientFile = origFile.getName();

          // Open connection to remote host and process file
          s  = new Socket(remoteHost, 55555);
          bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
          fr = new FileReader(origFile);

          System.out.println("Transmitting " + patientFile + " ...");

          // Send hospital name on first line
          bw.write(hospitalName, 0, hospitalName.length());
          bw.newLine();

          // Send patient name name on second line
          bw.write(patientFile, 0, patientFile.length());
          bw.newLine();

          // Send data as remaining lines
          while ((read = fr.read(c)) != -1)
             bw.write(c, 0, read);

          // Close shop
          bw.flush();
          fr.close();
          bw.close();
          s.close();

          // Rename original file to indicate it was processed
          renamedFile = new File(dataDir, fileList[i] + DONE);
          origFile.renameTo(renamedFile);

          patientsProcessed++;
       }

       System.out.println(patientsProcessed + " patients processed");
    }
}

Listing 7: ReceiveDataServer.java

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.zip.GZIPOutputStream;


/** Server for receiving client data (e.g. java ReceiveDataServer) */
public class ReceiveDataServer
{
    public static void main(String args[])
                       throws Exception
    {
        ServerSocket server = new ServerSocket(55555);
        Socket       client = null;

        // Display server info
        System.out.println(server);

        // Continuosly wait for client connections and process their data
        while (true)
        {
           // Accept a client connection, spawn a thread for it
           // and go back to listening for other clients
           client = server.accept();
           (new ProcessData(client)).start();
        }
    }
}



class ProcessData
      extends Thread
{
    Socket s = null;

    public ProcessData(Socket client)
    {
       s = client;
    }


    // This method is invoked when running in a separate thread
    public void run()
    {
       System.out.println("Thread started for client: " + s);

       DataInputStream  dis = null;
       GZIPOutputStream gos = null;
       String hospitalName  = null,
              patientName   = null,
              fileName      = null;

       try
       {
          // Receive incoming data using following protocol:
          // 1st line = hospital name
          // 2nd line = patient name
          // Remaining bytes are the patient’s data

          dis = new DataInputStream(s.getInputStream());
          hospitalName = dis.readLine();
          patientName  = dis.readLine();
          fileName     = patientName + ".gz";
          gos          = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
          byte b[]     = new byte[4096];
          int  c, total= 0;


          System.out.println("Reading and saving data as GZIP file for " + patientName);
          while ((c = dis.read(b)) != -1)
          {
             gos.write(b, 0, c);
             gos.finish();
             total += c;
          }

          // Close output file and socket
          gos.close();
          s.close();

          if (total < 1)
          {
             System.out.println("0 bytes received, no database action required");
             return;
          }

          // Now store GZIP/input file in database using JDBC
          storeInDatabase(hospitalName, patientName, fileName);
       }
       catch (Exception e)
       {
          System.err.println("Database error: " + e.getMessage());
          e.printStackTrace();
          return;
       }

       System.out.println("Done processing " + patientName);
       System.out.flush();
    }


    /**
      * Store data in a table named "Patients" with the following columns:
      * 1. hospitalName char(50)
      * 2. patientName char(50)
      * 3. patientData text (or memo in MS-Access)
      */
    private void storeInDatabase(String hospitalName,
                                 String patientName,
                                 String fileName)
                 throws Exception
    {
       System.out.println("Ready to save data in database for " + patientName);
       Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection        conn = DriverManager.getConnection("jdbc:odbc:Patients", "admin", "");
       PreparedStatement stmt = null;
       ResultSet rslt         = null;
       String sql             = null;
       int count;

       // See if record already exists
       sql   = " SELECT COUNT(*) FROM Patients WHERE patientName = ?";
       stmt  = conn.prepareStatement(sql);
       stmt.setString(1, patientName);
       rslt  = stmt.executeQuery();
       rslt.next();
       count = rslt.getInt(1);
       rslt.close();
       stmt.close();

       // Get file info and open input stream
       File fileInfo  = new File(fileName);
       int fileLength = (int)fileInfo.length();
       InputStream is = new FileInputStream(fileName);

       // Do either an INSERT or UPDATE 
       if (count < 1)
       {
           System.out.println("Record not found, doing an INSERT");
           sql = " INSERT INTO Patients"
               + " (hospitalName, patientName, patientData)"
               + " VALUES (?, ?, ?)";
           stmt = conn.prepareStatement(sql);
           stmt.setString(1, hospitalName);
           stmt.setString(2, patientName);
           stmt.setBinaryStream(3, is, fileLength);
       }
       else
       {
           System.out.println("Record found, doing an UPDATE");
           sql = " UPDATE Patients"
               +    " SET patientData = ?"
               +  " WHERE patientName = ?";
           stmt = conn.prepareStatement(sql);
           stmt.setBinaryStream(1, is, fileLength);
           stmt.setString(2, patientName);
       }

       // Close shop
       stmt.executeUpdate();
       stmt.close();
       conn.close();
       is.close();
   }
}</pre>
        </td>
    </tr>
</table>

Listing 8: ShowDbData.java
    
import java.net.*;
import java.sql.*;
import java.util.zip.GZIPInputStream;


/** Displays patient data in DB (e.g. java ShowDbData JohnSmith) */
public class ShowDbData
{
    public static void main(String args[])
                       throws Exception
    {
       // Check arguments
       if (args.length < 1)
       {
          System.err.println("usage: java ShowDbData patientName");
          System.exit(1);
       }

       // Initialize variables
       String patientName  = args[0];

       Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection        conn = DriverManager.getConnection("jdbc:odbc:Patients", "admin", "");
       PreparedStatement stmt = null;
       ResultSet rslt         = null;
       String sql             = null;
       InputStream is         = null;
       byte b[]               = new byte[4096];
       int count;

       // Query for data
       sql   = " SELECT patientData FROM Patients WHERE patientName = ?";
       stmt  = conn.prepareStatement(sql);
       stmt.setString(1, patientName);
       rslt  = stmt.executeQuery();
       rslt.next();
       is    = rslt.getBinaryStream(1);

       // For some reason, GZIP doesn’t work directly with the 
       // stream returned by JDBC, so we’ll use a temp file
       File tmpFile         = new File(patientName + ".tmp");
       FileOutputStream fos = new FileOutputStream(tmpFile);
       while ((count = is.read()) != -1)
          fos.write(count);
       is.close();

       // Decompress and display data to stdout
       is = new GZIPInputStream(new FileInputStream(tmpFile));
       while ((count = is.read(b)) != -1)
          System.out.write(b, 0, count);

       // Close shop
       is.close();
       fos.close();
       rslt.close();
       stmt.close();
       conn.close();

       // Delete temp file
       tmpFile.delete();
   }
}

      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.