More Java

Overview

While Java continues to change dramatically from version to version, most of the changes involve the introduction of new packages, classes, interfaces, and methods. The actual core syntax of the language is very stable, with the last major revision being the introduction of inner classes "way back" in JDK 1.1.0.

We want to ensure that this book provides a complete introduction to all the syntax in Java, and to all the major functionality in the JDK-supplied packages. The latter is reviewed later in this chapter, but first, some basic syntactical aspects of the language still need to be covered. For completeness, the following sections offer a very brief tour of the remaining syntax elements, although our feeling is the majority of RPG programmers moving into Java will not need to know them. (By "syntax," we mean any functionality that requires a modifier, keyword, operator, or other basic language element.)



Inner Classes

As discussed in Chapter 2, Java applications contain packages that contain classes (or interfaces), which contain static and instance variables and methods. Methods, in turn, contain parameters, local variables, and finally executable code. Chapter 2 doesn't mention, though, that classes themselves can contain other classes. That is, Java classes can have subordinate classes nested inside them, which are known as inner classes. A simple example is shown in Listing 14.1.

Listing 14.1: An Inner Class (EnumerateDept) Inside an Outer Class (Dept)

import java.util.*;
 
public class Dept 
{
 private int deptNbr; 
 private Emp deptMgr; 
 private Vector deptEmps = new Vector();
 
 public Dept(int nbr, Emp mgr) 
 {
 deptNbr = nbr; 
 deptMgr = mgr; 
 }
 
 // Dept methods not shown 
 public Enumeration getEnumeration() 
 {
 return new EnumerateDept(); 
 } // end method getEnumeration
 
 class EnumerateDept implements Enumeration 
 {
 private int pos=0;
 
 // end-of-list test... 
 public boolean hasMoreElements() 
 { 
 return (pos < deptEmps.size()); 
 }
 // retrieve next element... 
 public Object nextElement() 
 {
 Object retobj = null; 
 if (hasMoreElements()) 
 {
 retobj = deptEmps.elementAt(pos); 
 pos = pos+1;
 } 
 return retobj;
 }
 } // end EnumerateDept class
 
} //end class Dept

The full example of this class, including its helper Emp, is not shown here, but is available on the CD-ROM included with this book. The key part of this class is its nested or inner class, EnumerateDept. It is declared right inside the "outer class," Dept, which is the only class that ever needs to see it. The Dept method getEnumeration returns an instance of it, but defines the return type to be the interface Enumeration that the inner class implements.

Inner classes bring nothing new to the table. They offer no functionality that you can't get by simply coding the same class by itself, outside of its outer class. An inner class is just for programmer convenience, as it saves the trouble of working with a separate class, and hence a separate source file, for those classes that are truly nothing more than implementation details.

Inner classes can access all the variables of the outer class, including those marked private. The opposite is not true-the outer class cannot access the variables of the inner class. Further, inner class instances are scoped to the current instance of the outer class in which they were instantiated. They can directly access the instance variables of the outer class and can use the syntax "this.this" to refer to the outer class instance they are contained in.

Inner classes can also be defined inside blocks or methods. In these cases, they can also access the local variables and method parameters of their enclosing block or method. Java insists that you mark the local variables and methods final if you want to allow the inner classes to access them. This is because an instance of the inner class might "live on" long after the block or method has completed execution, and thus the value of the referenced local variables and parameters need to live on as well, and that means they need to be unchanging.

Listing 14.2 shows a modified version of Listing 14.1, with the inner class moved to be directly inside the only method that uses it. In addition to moving the inner class to be entirely within the getEnumeration method, this example also defines a local and final variable named empsCopy to hold a copy of the current contents of the employee Vector.

The inner class code is changed to use this copy. This is a good idea, since you want a returned enumeration object to be impervious to changes made to the department after you get the object. It should be a snapshot in time. In this example, the same effect could have been achieved by declaring empsCopy as an instance variable of the inner class.

Listing 14.2: An Inner Class Defined Directly Inside a Method

import java.util.*;
 
public class Dept 
{
 // ... same as Listing 14.1 ... 
 public Enumeration getEnumeration() 
 { 
 final Vector empsCopy = (Vector)deptEmps.clone();
 
 class EnumerateDept implements Enumeration 
 {
 private int pos=0;
 
 // end-of-list test... 
 public boolean hasMoreElements() 
 {
 return (pos < empsCopy.size());
 }
 // retrieve next element...
 public Object nextElement()
 {
 Object retobj = null; 
 if (hasMoreElements())
 {
 retobj = empsCopy.elementAt(pos); 
 pos = pos+1;
 }
 return retobj;
 } 
 } // end EnumerateDept class
 return (pos < empsCopy.size());
 } // end me thod getEnumeration
} // end class Dept


Anonymous Inner Classes

In an effort to save even more typing, Java also allows you to define an inner class right on the new operator. You simply place the class definition immediately after the new statement, just prior to the sericolon. In this case, you don,t even bother to name the class, hence the term anonymous inner class.

Listing 14.3 shows yet another revision of the example, using an anonymous inner class instead of a named inner class. Notice that the new operator in this case names the existing class or interface to create a new instance of, followed by a class body ended with a semicolon. The name given on the new operator must be a class or interface that already exists. If it is a class name, the subsequent class body is considered to extend that class. If it is an interface name, the subsequent class body is considered to implement that interface and extend java.lang.Object. The keywords extends and implements are not permitted on anonymous classes. Further, anonymous inner classes are not permitted to define constructors. If any parameters are given between the parentheses on the new operator, they are passed to the parent's matching constructor.

Listing 14.3: An Anonymous Inner Class

public Enumeration getEnumeration() 
{
 final Vector empsCopy = (Vector)deptEmps.clone();
 
 return new Enumeration()
 {
 private int pos=0;
 
 // end-of-list test... 
 public boolean hasMoreElements() 
 {
 return (pos < empsCopy.size());
 }
 // retrieve next element... 
 public Object nextElement()
 {
 Object retobj = null; 
 if (hasMoreElements())
 {
 retobj = empsCopy.elementAt(pos); 
 pos = pos+1;
 }
 return retobj;
 } 
  }; // end anonymous inner class Enumeration 
}// end method getEnumeration

Anonymous classes are most often seen in GUI and JavaBean coding, where you want to register your interest in a particular event. For example, the examples in Chapter 12 use addActionListener to register GUI components for action events, implement the ActionListener interface in a class, and supply an actionPerformed method that checks for the particular component that fired the event. Alternatively, for each addAction Listener call, you could use new with an anonymous class to pass an instance of an anonymous class that implements the actionPerformed method for just that component, for example:

okButton.addActionListener( new ActionListener () 
{
 public void actionPerformed(ActionEvent evt) 
 { 
 System.out.println("OK pressed"); 
 }
}; );

Inner classes, both named and anonymous, should only be used for very small classes that can be digested at a quick glance. Anything bigger, and they actually have a negative affect on the readability and maintainability of the code, completely defeating their purpose. Some programmers don't like to use them at all, but you will see them used heavily in the JDK documentation and examples.



Static Initializers

You have seen static variables, or class variables, since Chapter 2. These are class-level variables defined with the modifier static, indicating they have one memory location for the life of the application, versus one per instance of the class. Recall that you can initialize the static variables, like all variables, at the time they are defined, as follows:

static int nextAvailableID = 100;

When the JVM first loads your class into memory, it immediately allocates storage for all the static variables in the class, and then runs the initialization expression, if there is one (otherwise, it assigns the default initial value based on the variable type). Quite often, other things are of value to do at that initial-load time, as well. For example, you might want to open a connection to a database, or read an initial value from a file. Java allows for this. It allows you to define any block of code at the class level (that is, not inside a method) inside the usual curly braces, as long as you preface the block with the keyword static. The JVM will run these blocks of code at the time the class is first loaded into memory, for example:

static { MyOtherClass.doStuff(); }

Any references to methods must, of course, be static methods, unless you include code to instantiate an object, as usual. These blocks of code are called static initializers, and they are the static equivalent of a constructor. The alternative to a static initializer is simply a static method, which you explicitly call at the beginning of your application.

Because static initializers are run so early in the life of an application, there are restrictions on exactly what you can do in them. For example, you can't call any methods that throw exceptions unless you use try/catch to monitor for those exceptions. In general, you don't see too many people coding elaborate static initializers. They tend to heavily clutter a class. Further, it just doesn't sit well with some people to write executable blocks of code outside of a formal method. As with anonymous classes, the increment and decrement operators, and the conditional operator, you can avoid using them, but you probably can't avoid seeing them.



Java Native Interface (JNI)

We hate to admit it, but occasionally there are times when there is something you just cannot do in Java, and you are forced to revert to another language, such as C. As the packages supplied with the JDK get increasingly richer, this becomes less and less of an issue. However, Java does have built-in support for calling out of the language. One option is to call an external executable object, which is done using a supplied method and is covered shortly. This is not very efficient, though, and it does not allow you to exchange parameters. The other alternative is to call a function in a DLL (or a service program on OS/400). This runs in the same address space as the virtual machine, so it is usually more efficient and offers more opportunity to interoperate with the called function.

Java comes with built-in support for calling C functions in DLLs, using Java Native Interface or JNI. This is done by actually wrapping the C function in a Java method that uses the modifier native. This modifier is similar to abstract, in that when you use it, you don't define a method body, for example:

public native String getEnvironmentVariable(String name);

The modifier native tells Java that this method is not implemented in Java at all, but rather in C (usually), in a DLL somewhere. What DLL exactly? Well, you have to tell Java that, by coding a static initializer (now you know why these exist) in your class that loads that DLL into memory, for example:

static { System.loadLibrary("MyDLL"); }

This will use the static method loadLibrary in class System in package java.lang, to find the named DLL (or *SRVPGM) on disk and load it into memory. What function (or procedure) in the DLL is called when someone calls your native method? The function with the same name as your native method name. What parameters are passed to it? The same parameters that were passed to your native method. Of course, some data type mapping has to occur between the Java and C language data types, and that is done for you automatically. These mappings are well defined and documented in the JDK documentation.

Can the C function update the parameters? You bet! It is quite cool how Java does all of this underlying plumbing for you so seamlessly. Well, not that seamlessly, actually. There are APIs you will have to call to map the data types and "call back" from C to Java.

So, you "don't do C"? Not to worry. You can call any DLL (or service program on OS/400) written in any language that is compatible with C. That is, if you can call a procedure in a service program from ILE C, you can call it from Java, too. Yes, that means RPG IV procedures, too, but only as of V4R4, when the RPG IV runtime was made thread-safe. You could also call a C function that, in turn, calls an RPG program. However, if your goal is to call an RPG program instead of an RPG procedure inside a service program, the best answer is either to call it as a stored procedure as discussed in Chapter 13, or to use the AS/400 Toolbox for Java program-call class discussed in Appendix A.

JNI involves some other stuff, too. For example, it specifies a set of C APIs that can be called to go the other way. That is, from a C program (or any program that can call C APIs), you can instantiate a Java class and call methods on that class. These APIs are a core part of the JDK, so while they are not written in Java, all operating systems that support Java are required to support them as well. This is one way for RPG to call Java. Another is to use the command-analyzer API to run the RUNJVA command, and then perhaps use a data queue or data area to exchange data. (The AS/400 Toolbox for Java has Java classes for working with these from Java, as discussed in Appendix A.)

If you want to pass parameters, the JNI APIs provide the best option, but they are a bear to code using these C APIs. The good news is that V5R1 of RPG IV adds language support in RPG to make calling Java relatively easy. This includes language syntax for instantiating a Java object and calling methods in that object. Under the covers, the RPG runtime uses the JNI APIs to do this.

There is much more to know about JNI. To get the details, refer to the JDK documentation in jdk1.2.1docsguidejniindex.html, and the RPG IV reference manual for V5R1 or higher.



Java Remote Method Invocation (RMI)

Imagine that you have Java code running on a client (for example, Windows or a Web browser), and you want to call Java code running on a server. Perhaps that server Java code uses JNI for accessing an existing application or transaction, and you want to get the results to the client for display to the user. The options for this very common requirement are plentiful.

The Java-designed and defined way is via something known as Remote Method Invocation, or RMI. RMI is a means to do a remote procedure call (RPC), but in an object-oriented way. In the days of client/server, RPC was a popular way of remotely calling a function on the server, from the client (as defined in the Distributed Computing Environment, or DCE, world). In an OO world, the preferred way to do client-to-server communications is via distributed objects. This is a simple concept: a distributed object lives on the server, but has a thin proxy for client code. This proxy object appears to have all the same methods as the "real" object on the server, but when client code calls one of those methods, the call is actually shuttled to the server, where it is executed using the real object. Any updated parameters or returned values are returned to the caller. Preferably, the client code does not know or care that the object it appears to be working with is actually a proxy.

To do distributed object programming, somebody has to give you the engine to enable the magic shuttling across the network (officially called marshalling). This, for example, is what OMG's (Object Management Group) CORBA (Common Object Request Broker Architecture) is all about, and what Microsoft DCOM (Distributed Common Object Model) is all about. Java's answer is RMI, and it is a core part of the JDK, so it is available on every system that supports Java. By the way, the client can be any operating system, Web browser, or device that supports Java. And so can the server! It is up to you to decide what is the client and what is the server, although almost always the server is the system where our data, applications, or transactions are.

To use RMI to create a distributed object, you must do the following:

  • Create an interface that describes the methods you want to make available remotely on clients. This interface must import java.rmi.* and extend Remote (from java.rmi). Further, every method signature must be defined with throws RemoteException.
  • For the server side, create a class that implements your interface and code it as needed to perform the functions you desire. As per interface rules, you must supply implementations for all the methods in your interface. Your class must import java.rmi.* and java.rmi.server.*, and extend UnicastRemoteObject from package java.rmi.server. (If you can't extend, that's okay, but you have to do some extra work in your constructor.)
  • Your class must supply a main method that instantiates an instance of this class and does some work to make that object "distributable." This includes giving the object a name by which clients will refer to it (since classes can and usually do have multiple objects, each object needs a unique name, since the clients work with objects, not classes), and "registering" the object in the RMI registry. This is all done using the static method rebind in the Naming class in package java.rmi. Prior to this, you must also call the setSecurityManager method in the System class and pass it an instance of RMISecurityManager. By convention, you name your class the same as the interface, but add Impl (for implementation) to the end of the name.
  • For the client-side code that will use the distributed object, you declare an instance variable to be the interface type from step 1. Then, rather than using new to instantiate your class from step 2, you call the static method lookup in class Naming in package java.rmi. This method takes a URL that defines the name or IP address of the remote system containing the remote object, and the name given your server class object in step 2. It gives you back the reference to an object instance of a class that implements the client interface from step 1. This object is a proxy of the server object from step 2. That proxy object is automatically downloaded for you by Java. It finds this object on the server by looking in the RMI registry on the server, using the name as a key. Now, you simply call the methods in the proxy as though it were a normal local object, but taking care to monitor for RemoteException exceptions.

So, where does the class for that proxy object come from? You create it using the rmic tool from the JDK. Give it your class name, such as XXX , and it will give you back two class files: XXX_stub.class and XXX_skel.class. The former is the proxy class for the client, and the latter is the "gorp" or skeleton version of the server class, which does all the ugly communications code for each method. You simply place both of these on your server, and RMI takes care of finding and using them appropriately. You never refer to them explicitly in your code.

On the server, then, you have the following:

  • The actual server class that you wrote and want to distribute. It implements the interface you wrote that describes all the methods you want to expose to client code.
  • A main method for instantiating your server class into an object, and registering that object with the RMI registry. This method can either be in your server class itself or in a separate class altogether; it's your choice.
  • Additional classes, as needed. The key thing is that you only need to distribute a single object of a single class. After that, you can instantiate and return to the client as many objects of as many classes as you like. You simply supply a method in your distributed class that will return these objects.
  • A proxy stub class for your server class, generated via rmic. This will be located on disk and instantiated automatically by the RMI registry when your main method registers your object. It will also be sent to the client automatically via the RMI registry, when a client calls lookup. It handles the communications on the client side.
  • A skeleton class for your server class, generated via rmic. An instance of this class is automatically created when you register your object with the RMI registry and is run automatically by the RMI registry. Its job is to wait for method-call requests from the client stub object, turn those into method calls to your actual server object, and return the results to the client. That is, it handles the communications on the server side.

On the client, you have the client code you wrote that remotely uses an instance of your server class. It does this by calling lookup and getting back an instance of that client proxy stub class. This contains methods of the same name and signature as your server class, but marshals calls to these methods across the wire to your actual server object, via the corresponding server-side skeleton object.

What about that RMI registry? This is executable code that you must start running on your server for everything to work. To start it, simply enter the JDK command rmiregistry, or on Windows, type the following:

start rmiregistry

The registry waits for requests from server Java applications to register objects (using the Naming.rebind method), and for requests from client Java applications or applets to find and retrieve object stubs (with Naming.lookup).

With the registry running, you can run your server-side class by calling it from the command line using the java command, as usual. Unlike the usual way of running Java classes from the command line, however, you have to specify the -D option (to define an important "property") on the Java command line. Specifically, you have to define the name of a little security-policy file containing information about which classes on the server can be distributed:

java -Djava.security.policy=d:/rmi/mysrc/policy examples.hello.HelloImpl

Note there should be one space after java and one space after the final policy. In this case, a class named HelloImpl is running from the package examples.hello. The policy property fully qualifies a local policy file.

Here is an example of a policy file (named simply policy, with no extension) that gives all classes access to all distributed server objects:

grant {
 // Allow everything for now 
 permission java.security.AllPermission; 
};

Once your Java server class runs, its main method will set the security manager (a necessary detail) and instantiate an object and register it in the registry, as discussed in step 2 earlier, and shown here (taken from the JDK-supplied RMI tutorial):

// create and install a security manager 
if (System.getSecurityManager() == null)
 System.setSecurityManager(new RMISecurityManager()); 
try {
 HelloImpl obj = new HelloImpl(); // create our distributable object 
 // Bind this object instance to the name "HelloServer" 
 Naming.rebind("//myhost/HelloServer",obj); 
 System.out.println("HelloServer bound in registry"); 
} catch (Exception exc) {
 System.out.println("HelloImpl err: " + exc.getMessage()); 
}

This makes the object available to client code, as follows:

Hello remoteObj = null; // Hello.java is the remote object interface 
try {
 remoteObj = (Hello)Naming.lookup("//myhost/HelloServer"); 
} catch (Exception exc)
 System.out.println("Remote communication failed with: " + exc.getMessage());
 System.exit(0); 
}
System.out.println(remoteObj.sayHello());

Note that only the initial object stub typically needs to be remotely downloaded using lookup, and hence the registry. After that, methods in the remote server object can return instantiated objects to the client code as they would normally return objects to local code. If you test an RMI example on Windows running the registry, with your server and client classes on the same system, use localhost for the host name in the rebind and lookup calls. However, beware that lookup will fail if your PC is configured for DHCP!

We don't cover further details of RMI, since there is sufficient information in the JDK documentation. For these details, see the file jdk1.2.2docsguide miindex.html, where jdk1.2.2 is the directory where you installed the Windows JDK. (This assumes you have installed the JavaDoc documentation for the JDK.) Further, Daniel Darnell's excellent book JAVA and the AS/400 (29th Street Press, ISBN 1-58304-033-1) contains an example of using RMI on the AS/400. For an in-depth look at RMI, see Mastering Java 1.2, by John Zukowski (Sybex, ISBN 0-7821-2180-2).

What is important for you to take from this introduction to RMI is that it is a relatively easy way to write distributed objects in Java. It is a great way to write code that runs on a server, and hence has easy access to all your resources there, and then to distribute that code to a client, such as a Web browser, where it can be displayed to the user. The underlying communications protocol is TCP/IP by default, but RMI in JDK 1.3 does allow you to choose IIOP (Internet Inter-Orb Protocol) for a CORBA flavor. If you are only interested in doing Java-to-Java communications (versus, say, Java to C++), this is an easy way to achieve that.



Java Object Serialization

If you think about the RMI discussion, you will realize just how much work is being done for you. This is especially true when client code calls a remote method and passes objects as parameters. How does it pass these objects? Well, there really is only one way to pass data between any client and server: as a stream of bytes. That means all object parameters must be converted or flattened to a stream of bytes first, then sent across the network to the server method, which in turn must convert or unflatten the stream of bytes into an object. The conversion from an object to a byte stream and back again is referred to as serialization.

It gets even more complicated with objects that contain other objects as instance variables. Each contained object, and every object inside the contained object, must be serialized in order to send the entire main object across the network. On the other side, the server must know exactly the algorithm used to do this, so it can do exactly the same thing in reverse, to re-create the contained objects and the main object from the byte stream.

Serializing an object to a byte stream, and de-serializing it from a byte stream, is not only of value in RMI communications. You will often need to save the current state of an object to disk, and then retrieve it from disk later. The "old" way of doing this was to create your own code to write each of the variables out to disk manually, using your own conventions. For example, you might use a special character to denote the end of each element in an array, so that when you subsequently read it in again, you would know where each record ended. The built-in Java support for serialization makes saving the data in any object, and subsequently restoring it, trivial. The official term for this is called persistence. Via serialization, you persist an object to disk.

Whether serializing an object to pass as a parameter in an RMI method call or to persist it to disk, the important step is generating the byte stream. After that, it is a simple matter to send the stream to disk or over the network. So how do you produce this stream? Any class that you want to be able to serialize must be defined to implement the Serializable interface from package java.io. This interface contains no methods; it is only used as a flag to the compiler and runtime.

To actually write your object to disk, then, use the ObjectOutputStream class from the java.io package. Instantiate it, passing a FileOutputStream object for the target file (a class from java.io for writing streams to disk), and then use the writeObject method to write objects to the stream, and therefore to the file. After writing, call flush to force the buffer to disk, and then close the file output stream.

For example, Listing 14.1 showed a class named Dept, and you will see on the CD-ROM that it has a main method, which creates an instance of the class and populates it with data. We have changed that class to be named DeptSerializable, and implemented the Serializable interface:

import java.io.*; 
public class DeptSerializable implements Serializable

The only wrinkle is that all instance variable objects in the class must also be of a serializable class type. Thankfully, almost all the JDK-supplied classes are serializable. However, this class contains instances of another class, Emp, which we also had to change to implement Serializable, and which we renamed to EmpSerializable. Next, to test writing to disk, we changed the main method of DeptSerializable to serialize the instance of DeptSerializable it creates and populates. We serialized it to file  Dept.ser, using a FileOuputStream object, an ObjectOutputStream object, and calling the method writeObject in ObjectOutputStream, as shown in Listing 14.4.

Listing 14.4: The Serializable Dept Class (main Method Only)

import java.util.*; 
import java.io.*;
 
public class DeptSerializable implements Serializable 
{
 // .. same ... 
 public static void main(String args[]) 
 {
 int mgrDeptId = 100; 
 int empDeptId = 200; 
 EmpSerializable mgr = new EmpSerializable(1, "Jim Bean", mgrDeptId); 
 DeptSerializable dept = new DeptSerializable(empDeptId, mgr); 
 dept.addEmployee( new EmpSerializable(20, "George Farr", empDeptId)); 
 dept.addEmployee( new EmpSerializable(20, "Phil Coulthard",empDeptId)); 
 dept.addEmployee( new EmpSerializable(21, "John S Page", empDeptId)); 
 dept.addEmployee( new EmpSerializable(22, "Sam Let", empDeptId)); 
 dept.addEmployee( new EmpSerializable(22, "Eli J Bean",empDeptId));
 
 try 
 {
 FileOutputStream ostream = new FileOutputStream("Dept.ser"); 
        ObjectOutputStream serStream = new ObjectOutputStream(ostream);
        serStream.writeObject(dept);
 serStream.flush(); 
 ostream.close(); 
 System.out.println("Dept written to Dept.ser"); 
 }
 catch (Exception exc) 
 {
 System.out.println("Error serializing: " + exc.getMessage()); 
 exc.printStackTrace();
 } 
 } // end main method 
} // end class DeptSerializable

Note how the serializing code is enclosed in a try/catch block, as it can throw exceptions. When you run this class, it runs to completion, creating a file named  Dept.ser in the current directory. To test reading this file in from disk, do the functional opposites of everything: a FileInputStream class, ObjectInputStream, and readObject, as shown in Listing 14.5.

Listing 14.5: Code to Re-Instantiate a Serialized DeptSerializable Class

import java.util.*; 
import java.io.*;
 
public class TestDeptSerializable 
{
 public static void main(String args[]) 
 {
 try
 {
 FileInputStream istream = new FileInputStream("Dept.ser"); 
        ObjectInputStream serStream = new ObjectInputStream(istream); 
        DeptSerializable dept =
           (DeptSerializable)serStream.readObject(); 
        istream.close();
 printDept(dept);
 } 
 catch (Exception exc) 
 { 
 System.out.println("Error reading from disk: " + exc.getMessage());
 } 
 } // end main method
 public static void printDept(DeptSerializable dept) 
 {
 Enumeration allEmps = dept.getEnumeration(); 
 System.out.println(); 
 String hdr = "Employees of dept " + dept.getNbr() + ":"; 
 System.out.println(hdr); 
 for (int idx=0; idxend TestDeptSerializable class

Notice how a printDept method is supplied and called to verify that all the data was read in properly. Running this gives the following:

Employees of dept 200: 
---------------------- 
Jim Bean (mgr)
George Farr
Phil Coulthard
John S Page
Sam Let
Eli J Bean

Listing 14.4 saved an object to disk, including all its data, while Listing 14.5 read that object in from disk and brought it back to life. You must admit, this is pretty good. It is a very easy way to save state or data persistently between runs of an application, with only five lines of code to save it and four lines of code to restore it (not including the try/catch code). You could also use it with the JDK-supplied classes for writing TCP/IP or HTTP communications code, for sending objects across the network. All of this is done for you when using RMI. If you have variables that are sensitive (such as password or salary), you can use the transient modifier on them. Any transient variables are not saved when their enclosing object is serialized, and they are set to their type's default value when the object is de-serialized.

There are other capabilities in Java's serialization support, such as dealing with parent classes that are not serializable, and dealing with multiple versions of an evolving class.

For more detailed information, see the JDK supplied documentation in the file jdk1.2.2docsguideserializationindex.html, where jdk1.2.2 is the directory in which you installed the Windows JDK.



Java Supplied Packages

The Java language is often referred to as a platform. Why? Because the intention is for you to write code that is operating-system independent. Keep in mind that the Java "compiler" (javac) generates byte code for the Java Virtual Machine. It does not generate machine code for the specific machine it happens to run on. Furthermore, to achieve the Java "write once, run anywhere" goal, you need to be able to write Java code that successfully avoids calling operating-system APIs or services.

That is certainly the case for applets; they are not even permitted by the Java security manager to call out to system services and APIs, or to any local program or DLL. Java applications and servlets have more options, of course, including the option to invoke native system commands and C code. Again, though, the intent is to decrease (as much as possible) your dependence on the underlying operating system. That will increase the portability of your code. To this end, the Java language must supply to the programmer as much functionality as possible to avoid the need to call operating-system services. This is the root of the reference to Java as a "platform": programmers are programming to Java and its set of supplied functions. This is in contrast to the traditional style of programming to the target operating systems' APIs.

By now you will surely agree that the Java language itself deserves credit for being a reasonably good language because it has the power of object orientation, but is not too complex. On top of this base, further functionality is provided by the class libraries, or packages, supplied with the language, as shown in Figure 14.1. This shields you from the need to "break out" (access the operating system). The richer these packages are (and they are getting richer with each release), the more function you have built-in. Another big advantage is that, because the packages are ported everywhere with the JVM, you can depend on having an impressive amount of functionality available wherever you go.


Figure 14.1: Java's application architecture

You are already familiar with some of the class libraries or packages that follow the naming convention java.xxx. For example, you have seen java.sql (for JDBC), java.math (for BigDecimal), and javax.swing and javax.swing.event (for user interfaces). You have used java.util for Date, Vector, HashTable, and Enumeration, and java.text for SimpleDateFormat. You have also used many of the classes in java.lang, the "default" package that is automatically imported by Java. You have seen such classes as java.lang.Object, java.lang.Class, java.lang.Thread, and java.lang.String.

Table 14.1 lists the interesting "core" packages supplied with Java up to 1.4.0. The package names that start with java. were designed to be part of the core Java specification. The package names that start with javax. started out as optional, separately downloadable packages. The packages not described in Table 14.1 are probably not relevant to your first business application.

Table 14.1: JSDK 1.4.0-Level Packages

Package

Description

javax.accessibility

Assistive technologies, including alternative user inputs.

java.awt

An abstract windowing toolkit for user-interface components.

java.awt.color

Support for color spaces.

java.awt.datatransfer

For transferring data between different applications and within the same application; most commonly used for drag-and-drop and the clipboard.

java.awt.dnd

Drag-and-drop support.

java.awt.event

An abstract windowing toolkit for user input events.

java.awt.font

Support for Type 1,2 Multiple Master fonts; OpenType; and TrueType fonts.

java.awt.geom

2-D geometry for advanced graphics manipulation.

java.awt.im java.awt.im.spi

Input Method framework for alternative DBCS input.

java.awt.image java.awt.image.renderable

For images and creating rendering-independent images.

java.awt.print

General printing classes and interfaces.

java.beans java.beans.beancontext

JavaBean classes and interfaces. A bean context is a container for beans, and defines the execution environment for the beans it contains.

javax.crypto javax.crypto.xxx

New for 1.4.0, a pluggable architecture for working with images stored in files and accessed across the network.

javax.imagio javax.imagio.xxx

For cryptographic operations; part of the core JSDK as of 1.4.0.

java.lang.ref

Provides weak references to objects so they'll still be garbage collected.

java.lang.reflect

For getting reflective (dynamic) information about classes and objects.

java.math

Math classes. (Hey, just like high school!)

javax.naming javax.naming.xxx

For naming and directory services, such as LDAP.

Package

Description

java.net

Networking classes for those interested in writing their own communications code, versus using the AS/400 Toolbox for Java, for example. Makes TCP/IP and HTTP communications relatively easy.

javax.net javax.net.ssl

Factories for client and server socket creation, both normal and secure. Allows encapsulation of socket creation.

java.nio java.nio.xxx

New for 1.4.0, these I/O (NIO) APIs provide new features and improved performance in the areas of buffer management, character-set support, regular-expression matching, file I/O, and scalable network I/O.

javax.print javax.print.xxx

New for 1.4.0, a framework, like JDBC. The Java Print Service enables clients and servers to discover and use print services for report writing.

java.rmi java.rmi.xxx

For remote method invocation (RMI).

java.security java.security.xxx

For security, including certificates, digital signatures, and message digests. See docs/guide/security/index.html.

javax.sound.midi javax.sound.midi.spi

New for 1.4.0, for working with MIDI recorded sound and a framework for service providers.

javax.sound.sampled javax.sound.sampled.spi

New for 1.4.0, for sampled audio data and a framework for service providers.

javax.swing javax.swing.xxx

New User Interface controls, layout managers, and models, plus supporting packages; discussed in Chapter 12.

javax.transaction javax.transaction.xa

For transaction managers.

java.util

Utility "value-added" classes like Vector, HashTable, and Date.

java.util.jar java.util.zip

For reading and writing .jar files and .zip files.

java.util.logging

New in 1.4.0, for logging messages during runtime.

java.util.prefs

New in 1.4.0, for saving and restoring preferences.

java.util.regex

New in 1.4.0, for regular expression support (pattern matching).

javax.xml.xxx org.xml.sax.xxx org.w3c.dom

For the mapping of OMG CORBA to Java, including an ORB, plus supporting packages.

org.orrg.CORBA org.orrg.xxx

For the mapping of CORBA to Java, including an ORB, plus supporting packages.



Common How do I? Questions

To round out your introduction to Java, the following sections answer some questions that we have been asked by AS/400 programmers who were learning Java, or that arose as we were learning and writing in Java. Although this information refers to packages and/or classes not included in this book, once you know where to look, you can easily refer to online documentation.

Note that the following sections answer the questions in the context of Java running on Windows. For Java running on OS/400, the answers are the same, but pertain to the Integrated File System (IFS) versus the Windows file system. If you want to have similar functionality when accessing the IFS file system from a remote client, see the AS/400 Toolbox for Java, which offers IFS classes that extend these built-in JDK classes, but which work against the OS/400 Integrated File System, even when run from a remote Windows or any other client. The classes shown in the following sections also work against the IFS, but only for Java running on the AS/400 itself.

How do I determine the working directory?

To determine the working directory, use the System.getProperty("user.dir") static method call. This returns a String object that contains your current working directory. For example, see Listing 14.6.

Listing 14.6: Querying the Current Directory-System Property

public class TestWorkingDir 
 {
 public static void main(String args[]) 
 {
 String currDir = System.getProperty("user.dir"); 
 System.out.println("Current directory: " + currDir);
 }
 }

Compiling and running this gives something similar to the following:

>java TestWorkingDir 
Current directory: F:JavaForRPGProgrammerssourcech14

When running on the AS/400, this is the current directory of your QSHELL environment if running there, or as set by the CHGCURDIR CL command. The initial value for this comes from the HOMEDIR value of your user profile, which, if not set, defaults to /, or the root directory. If you don't have a user directory in the IFS yet, you should create one with CRTDIR, and change your profile to point to it.

How do I change the working directory?

The getProperty method inside System is very handy. It has a well-defined list of the properties you can query. Some of them, such as user.dir, are dynamic, while others are hard-coded by the JVM vendor, such as os.name. In addition to getProperty, there is also a setProperty method for changing a property, but only for those that make sense, such as user.dir to change the current directory, as shown in Listing 14.7.

Listing 14.7: Changing the Current Directory-System Property

public class TestSetWorkingDir 
{
 public static void main(String args[]) 
 { 
 System.out.println("Was: "+System.getProperty("user.dir")); 
 System.setProperty("user.dir","c:\phil"); 
 System.out.println("Is : "+System.getProperty("user.dir")); 
 }
}

Notice the double backslash in Listing 14.7. Because the backslash is an "escape" character in Java strings, you must use a double backslash to get just one. This situation is similar to what you find in the C and C++ languages. Compiling and running this example gives something similar to the following:

F:JavaForRPGProgrammerssourcech14>java TestSetWorkingDir 
Was: f:JavaForRPGProgrammerssourcech14
Is : c:phil

We do not recommend using the hard-coded backslash character here because not all file systems use it. For example, on UNIX and OS/400 you would need to use a forward slash. A good portable citizen will use the Java-supplied constant pathSeparatorChar in class File in package java.io. This will be defined properly for each system on which you run your Java application.

What are the other system properties you can query? The best answer to this is to write and run a little program that queries and displays them all, as in Listing 14.8.

Listing 14.8: Code to Retrieve and Print All System Properties

import java.util.*; 
public class GetAllProperties 
{ 
 public static void main(String args[]) 
 {
 Properties allProps = System.getProperties();
 allProps.list(System.out); 
 }
}

If you run this on Windows, you get output like the following:

- listing properties - 
java.specification.name=Java Platform API Specification 
awt.toolkit=sun.awt.windows.WToolkit 
java.version=1.2.2 
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment 
user.timezone=America/New_York 
java.specification.version=1.2 
java.vm.vendor=Sun Microsystems Inc. 
user.home=C:WINNTProfilescoulthar 
java.vm.specification.version=1.0
os.arch=x86
java.awt.fonts= 
java.vendor.url=http://java.sun.com/
user.region=US
file.encoding.pkg=sun.io 
java.home=C:Program FilesJavaSoftJRE1.2 java.class.path=F:IBMDebuglibdertrjrt.jar;.;classe... 
line.separator= 
java.ext.dirs=C:Program FilesJavaSoftJRE1.2lib... 
java.io.tmpdir=C:TEMP
os.name=Windows NT 
java.vendor=Sun Microsystems Inc. 
java.awt.printerjob=sun.awt.windows.WPrinterJob 
java.library.path=C:WINNTsystem32;.;C:WINNTSystem32...
java.vm.specification.vendor=Sun Microsystems Inc. 
sun.io.unicode.encoding=UnicodeLittle 
file.encoding=Cp1252 
java.specification.vendor=Sun Microsystems Inc. 
user.language=en
user.name=coulthar 
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport... 
java.vm.name=HotSpot VM
java.class.version=46.0 
java.vm.specification.name=Java Virtual Machine Specification 
sun.boot.library.path=C:Program FilesJavaSoftJRE1.2in 
os.version=4.0
java.vm.version=1.0.1 
java.vm.info=1.0.1, mixed mode, build g
path.separator=;
file.separator= 
user.dir=F:JavaForRPGProgrammersSOURCEch14Listing14-8 
sun.boot.class.path=C:Program FilesJavaSoftJRE1.2lib...

We leave it to you to run this on AS/400 to see what you get.

How do I list files and directories?

To list files and directories programmatically, use the File class in package java.io. It will be worth your while to explore this rich class! The example in Listing 14.9 lists all files in the current working directory.

Listing 14.9: How to List Files in a Directory

import java.io.File;
 
public class TestList 
{ 
 public static void main(String args[]) 
 { 
 String currDir = System.getProperty("user.dir"); 
 File listFiles = new File(currDir); 
 String[] allFiles = listFiles.list(); 
 for (int idx = 0; idx < allFiles.length; idx++)
 System.out.println(allFiles[idx]); 
 }
}

This gives something similar to the following:

F:JavaForRPGProgrammerssourcech14Listing14-9>java TestList 
TestList.java
TestList.class

The File class represents either a directory or a file (a stream file as found in Windows or the IFS on AS/400). Which one depends on what name you give it in the constructor. Once you have a File object, there are many cool methods available in it, like isDirectory, exists, mkdir, and delete. The list method used is only valid if the File object represents a directory, and returns an array of names of the files in that directory. There is also a static listRoots method as of JDK 1.2.0 that returns all the root directories in the form of an array of File objects.

To subset the list, specify a FilenameFilter object on the list method call. You must create your own class that implements this interface, and code in an accept method that takes as input a directory name in the form of a File object, and a file name as a String. Java will call this method for each file in the directory, and you have to decide whether to return true, indicating the name should be considered part of the list. For example, to subset the list to retrieve all .java files, you need the class shown in Listing 14.10.

Listing 14.10: A File-Name Filter

import java.io.*;
 
public class JavaFileFilter implements FilenameFilter 
{
 public boolean accept(File dir, String name) 
 {
 return (name.endsWith(".java"));
 }
}

When calling the list method, the next step is to specify an instance of this class in the previous example:

String[] allFiles = listFiles.list(new JavaFileFilter());

Now, the previous TestList class gives a subset list:

F:JavaForRPGProgrammerssourcech14Listing14-10>java TestList 
TestList.java
JavaFileFilter.java

Here is a final tip regarding the File class: You will often write code that needs to retrieve the name of a file from a File object. There are many methods in the File object for this, but the ones you want are getName to get the unqualified file name, getParent to get just the path name, and getAbsolutePath to get the fully qualified file name.

How do I list drives?

As of JDK 1.2.0, there is now a way to retrieve a list of drives on your current system. This is done using the listRoots static method in the File class. This method returns an array of File objects, as shown in Listing 14.11.

Listing 14.11: Listing Drives Using File's listRoots Method

import java.io.*;
 
public class TestListDrives 
{
 public static void main(String args[]) 
 {
 File drives[] = File.listRoots();
 for (int idx=0; idx < drives.length; idx++)
 System.out.println(drives[idx]); 
 }
}

This produces the following on our Windows NT system:

A:
C:
D:
E:

How do I create a directory?

To create a directory, you use the java.io.File class once again. The mkdir (make directory) method is the one you want. First, you must instantiate an instance of File that contains the fully qualified new directory name, as shown Listing 14.12.

Listing 14.12: How to Make a Directory Using File's mkdir Method

import java.io.*;
 
public class TestMkDir 
{
 public static void main(String args[]) 
 {
 String currDir = System.getProperty("user.dir"); 
 String newDir = currDir + "\newdir"; 
 File dirObject = new File(newDir); 
 System.out.println("mkdir " + dirObject.getAbsolutePath()); 
 boolean ok = dirObject.mkdir(); 
 System.out.println("result was: " + ok);
 }
}

This produces output similar to the following:

F:JavaForRPGProgrammerssourcech14Listing14-12>java TestMkDir mkdir 
F:JavaForRPGProgrammerssourcech14Listing14-12
ewdir 
result was: true

How do I create or open a flat file and write to it?

Contrary to what you might expect, flat files (such as those on your workstation drives) are not created using the java.io.File class. (Well, there is a way as of JDK 1.2.0 using the createNewFile method in that class, but typically you won't use it.) Instead, you create a file by using the "zoo" of stream classes in the java.io package. This package contains numerous interfaces, classes, and class hierarchies for dealing with streams. (Streams are input or output devices that are read or written bytes at a time.) These classes are designed to be nested. They use one object to create, and another to get the desired input/output interface. Some examples are buffered or unbuffered, random or sequential, byte at a time or line at a time, and binary or text.

What you need to know for our purposes is that, to create a file, you will typically use the FilterOutputStream class. This passes the File object that was created with the file path and the intended file name into the constructor. If the file exists, however, it will be replaced. If you want to first check for the existence of a file, use the File object's exists method. If your intention is to write to the file a line at a time, nest the FileOutputStream object inside a PrintWriter object. That will access that class's println (print line) method, as shown in Listing 14.13.

Listing 14.13: Creating and Writing to a Stream File

import java.io.*;
 
public class TestWriteFile 
{ 
 public static void main(String args[]) 
 { 
 File outFile = new File(System.getProperty("user.dir"),"myFile.tst"); 
 try
 {
 PrintWriter outFileStream = 
 new PrintWriter(new FileOutputStream(outFile)); 
 outFileStream.println("test");
 outFileStream.flush();
 outFileStream.close();
 } 
 catch (IOException exc) 
 { 
 System.out.println("Error opening file " + outFile.getAbsolutePath()); 
 System.exit(1);
 }
 System.exit(0); 
 } // end main method 
} // end TestWriteFile class

The next step is to write lines to this file with the println method. As you have seen all along, use the following System.out.println:

outFileStream.println("Hey, I get it now!");

Because PrintWriter is a "buffered" stream, println does not immediately write to disk. For performance reasons, that happens only when its internal buffer is full. If this causes a problem, specify true as a second parameter to its constructor, or explicitly call its flush method after every call to println. Finally, you should make it a habit to call close when you are done with a file to unlock it and free its resources.

How do I read a flat file?

For both writing to an output flat file and reading from an input flat file, use the plethora of classes in the java.io package. We assume that you want the file to be read efficiently (buffered) and to be read as text, not binary. If that is the case, you open an existing file using a nested-class approach, as shown in Listing 14.14. Notice in the listing that the readLine method was used to read the file a line at a time.

Listing 14.14: Reading the Contents of a Stream File

import java.io.*; 
public class TestReadFile 
{ 
 public static void main(String args[]) 
 { 
 File inFile = new File(
 System.getProperty("user.dir"),"TestReadFile.java"); 
 try
 {
 BufferedReader inFileStream = 
 new BufferedReader( 
 new InputStreamReader( 
 new FileInputStream(inFile) ) ); 
 boolean done = false;
 while (!done)
 {
 String line = inFileStream.readLine(); 
 if (line != null)
 System.out.println(line); 
 else
 done = true;
 } // end while loop 
 inFileStream.close(); 
 } catch (IOException exc) {
 System.out.println("Error reading " + 
 inFile.getAbsolutePath());
 }
 System.exit(0); 
 } // end main method 
} // end class TestReadFile

How do I call another program or command?

While not heartily recommended for portable applications, there are times when you might find it necessary to invoke a local program object on your Windows system. You learned earlier in this chapter about using JNI for calling a DLL or service-program entry point, but sometimes you want to run an executable, not call a DLL function. There is a way to do this in Java that uses the Runtime class in the java.lang package. This class represents your Java runtime system. To get an instance of this class, code Runtime.getRuntime(). This returns an object of type Runtime, appropriately enough.

You will find a number of useful features in this class for performing functions such as tracing calls, querying available memory, and forcing the garbage collector to run. This is also the class where you find the exec method, which you can use to execute other programs or commands. It is, then, roughly equivalent to the command analyzer API (QCMDEXEC) on the AS/400. There are actually a number of versions of exec, but typically you need only the one that takes one string, which contains the command and its parameters.

This discussion is really only for calling executables on clients from Java running on clients. While it can be used by Java running on the AS/400 to call a CL command, you would only consider doing so if you were concerned about portability to other server platforms. The reason we say this is that the AS/400 Toolbox for Java provides a much richer way to do this, via its program-call class, which includes support for parameter updates and easy retrieval of logged messages. Indeed, the Toolbox has PCML (Program Call Markup Language) to simplify this even more. In fact, you can now even use this to access procedures in a service program, so the JNI discussion earlier was really only applicable to non-AS/400 Java code. The other benefit of using the Toolbox is that your Java code can run either on the AS/400 or remotely from any client. However, if you are interested in how to call non-AS/400 executables, read on.

The exec method in Runtime returns an object of type Process for managing the running command. The command will be started as soon as you use the exec method, but there are methods in the returned Process object you can use to wait for the command to finish running and to query its returned value. One such method gets the return code the command specifies when it exits. Also, you will probably want to capture and query any messages it issues to standard output or standard error. Process has methods for retrieving these as well.

You can use the ready-made class in Listing 14.15 to run a given command string. It runs the command and captures its standard output and standard error lines as Strings in a Vector object, which you can subsequently cycle through.

Listing 14.15: A Class for Running Any Local Executable and Capturing Messages

import java.util.*; // for Vector 
import java.io.*;
public class TestRunCmd
{
 Vector output = new Vector();
 
 public boolean runCmd(String cmd) 
 {
 boolean ok = true;
 System.out.println(cmd); 
 Process process;
 try { 
 process = Runtime.getRuntime().exec(cmd); 
 } catch (IOException exc) { 
 System.out.println("Error running command: " + cmd); 
 return false;
 }
 String line; 
 // capture standard error 
 DataInputStream err = new DataInputStream(process.getErrorStream()); 
 BufferedReader berr = new BufferedReader(new InputStreamReader(err)); 
 try { 
 while ((line = berr.readLine()) != null) output.addElement(line); 
 } catch(IOException exc) {}
 // capture standard output 
 DataInputStream in = new DataInputStream(process.getInputStream()); 
 BufferedReader bin = new BufferedReader(new InputStreamReader(in)); 
 try { 
 while ((line = bin.readLine()) != null) output.addElement(line); 
 int rc = process.waitFor(); 
 } catch(Exception exc) {}
 return ok;
 } // end runCmd method
 public void processOutput()
 { 
 if (!output.isEmpty()) 
 { 
 for (int idx = 0; idx < output.size(); idx++)
 System.out.println((String)output.elementAt(idx)); 
 output.removeAllElements();
 }
 }
} // end class TestRunCmd

The main method included for testing this is as follows:

public static void main(String args[]) 
{
 if (args.length != 1)
 System.out.println("Syntax: TestRunCmd command-to-run"); 
 else 
 { 
 TestRunCmd me = new TestRunCmd(); 
 if (me.runCmd(args[0])) 
 me.processOutput();
 } 
} // end main method

This takes any command as a parameter and executes it from Java. If you try the following, the Windows Notepad editor will come up:

java TestRunCmd notepad

Try the following to see the javac compiler compile the file:

java TestRunCmd "javac TestRunCmd.java"

If there are errors (try making some), you will see them written out to the console.

We went back to the PDM options application discussed in Chapter 13, in the  PDMOptionsThreaded directory, and used the new TestRunCmd class to process the Print button there. We were already printing the PDM options to a local file, so we subsequently used this class to run the command "CODEEDIT filename /C LP_PRINT" on that file. This opened the CODE/400 editor on the file and brought up the print dialog so that we could select a printer and print the list.

How do I save and restore user profile information?

In your programs, you will probably often want to record user-profile information, such as preferences. To do this in Java, use a class named Properties from the java.util package. The Properties class is a handy utility class for storing key/value pairs, which Java refers to as properties. The Properties class extends the Hashtable class, adding the methods setProperty and getProperty. These just call their parents' put and get methods, but are defined to accept String values, for convenience (since no casting is required). It also adds methods for saving the contents to disk and reading them back from disk. Properties is a table of Strings that can hold any information you want to put in it. Each String object is indexed in the table using another String value for the key. So, you might store information like this about a user:

Name (Key)

Value

"LIBRARY"

"MYLIB"

"NUMBER"

"0011001"

These might represent, for example, the values previously entered into entry fields. That will allow you to default these entry fields with the same values on the next use of your application.

To store this information, create an instance of the Properties class and use the setProperty method, like this:

Properties profile = new Properties(); 
profile.setProperty("LIBRARY", libraryEntryField.getText()); 
profile.setProperty("NUMBER", numberEntryField.getText());

At this point, the information only exists in memory. To save it to a local flat file, use the store method. (JDK 1.1.x used a save method, but this is deprecated now.) Specify a FileOutputStream object for the file to save it to and a header String to be placed as the first line in the file for documentation. We have already discussed how to create or replace an output flat file as follows:

File outFile = new File("c:\", "preferences.ini"); 
try { 
 profile.store(new FileOutputStream(outFile), "User Preferences");
} catch (Exception exc) {
 System.out.println("Error saving profile: " + exc.getMessage()); 
}

You can be as creative as you want to be with the file name and extension. Some common extensions are .dat, .txt, .ini, and .prf, but the choice is yours.

When saving to a local file, it is often safe to assume that there is only one user per workstation, so any file name will do. However, if you decide to store the files on a common server, you should probably name the file with the user's ID.

Now that you have this information saved, you need to know how to read it in again. That's a piece of cake. Define a Properties object and use its load method, specifying a FileInputStream as a parameter. This will populate the Properties object from disk, after which you can use the getProperty method to read individual values from it. Here is an example:

Properties profile = new Properties(); 
try {
 File inFile = new File("c:\", "preferences.ini"); 
 profile.load(new FileInputStream(inFile)); 
 String libName = profile.getProperty("LIBRARY","QGPL"); 
 String number = profile.getProperty("NUMBER"); 
} catch (Exception exc) {}

The getProperty method allows an optional second parameter to return if the asked-for property does not exist. Again, you ask for a property by passing the key value for that property, and get back the value for that property.

When defining a Properties object, you can also specify another Properties object in the constructor. Here is an example:

new Properties(defaultProperties);

The values in the other object are used as defaults that "prime" the new object. This is a convenient way to specify default values for those entries not found in the file on disk.

You have seen the Properties class before. Recall that Chapter 13 mentions that the JDBC getConnection method allows you to specify a Properties object as input for giving all the property values. Although we didn't show it, we also used Properties in the PDM Options GUI application in Chapter 12. The  PDMOptionsWindow class uses this to save the name of the last edited PDM options file, and to restore it and use it as the default on subsequent runs of the application.



JavaBeans

Now that you have seen examples of some common programming situations in Java, let's turn to some common Java-related terms you might have heard. For example, can you define JavaBeans? If not, don't be afraid that you don't know beans! JavaBeans are not very mysterious; they are simply "dressed up" Java classes. In other words, they are Java classes that follow a few conventions and, in some cases, add a little functionality to a typical class. Their purpose is to make it easier for tools like VisualAge for Java to work with a class.

As you will see, JavaBeans are classes that have a default constructor, properties (any value settable by setXXX or gettable by getXXX), and methods (any non-get/set method defined as public). In addition, beans can have bound properties (monitorable for changes) and events (monitorable triggers, such as a button pressed or an inventory threshold exceeded). Further, your bean classes can also supply property editors and customizers that tools will launch when programmers work with your beans.

Properties

Java is all about reuse. One way to get reuse is to design good base classes that can easily be extended, as you have seen from Chapter 9 on. Another way-just as important and needed-is to design a class that can be adapted easily by setting variable values. For example, imagine writing a GUI class that is a drop-down list showing all the fields from a particular database file, record, and field. To tailor this for another database, you could write a new class that extends the first and overrides this information. Alternatively, you could simply supply methods to set the name of the file, record, and field to list from.

In many cases, you can design highly adaptable classes simply by supplying methods to change key information. By "variablizing," you can make a class that is highly reusable in many situations. The "variables" are probably actual instance variables, but you usually mark them as private, and supply methods to set and get them. So, you might have methods named setDatabaseName and getDatabaseName, for example. These variables are called "properties" in JavaBeans. Simply supplying get and set methods makes the class JavaBean-compliant.

In VisualAge for Java and other tools, when you drop a class onto the GUI Builder window, an instance of the class is created (using the default constructor), and you can subsequently double-click on that object to see and set its properties. The property sheets simply list all getXXX methods, but without the "get" part. For example, you might see a property named databaseName on this sheet. You would be allowed to directly edit those values that have a corresponding setXXX method. When you do change a property value, most tools generate a call to setXXX with your value as the parameter. Note that if your property is a boolean value, you should name the method isXXX instead of getXXX, as in isEnabled, isVisible, or isConnected.

Sometimes your property is actually a list, not a single value. For example, your Depart ment class might have an Employee property that is, internally, an array of employees. In this case, your set and get methods will require an integer index value as a parameter indicating which employee to set or get (an index into the list). These types of properties are known as indexed properties.

One last point about properties: They can be stored internally as private instance variables, or they can be computed on the fly. Because you always access them by getXXX and setXXX, what happens under the covers is not important. So, for example, a getPay method might compute the pay property from salary and payPeriod variables.

Listing 14.16 shows a simple JavaBean class that records the current amount of stock in inventory. The class is a JavaBean because it has a default constructor and set and get methods, meaning it has properties (specifically, it has one inStock property). It also has other public methods that users of the bean can call, namely incrementInStock and decrementInStock.

Listing 14.16: A Simple JavaBean-Compliant Class for Tracking Inventory Stock

public class Inventory 
{ 
 protected int inStock = 0; 
 protected static final int INITIAL_STOCK = 100;
 
 public Inventory() 
 {
 inStock = INITIAL_STOCK; 
 }

 public int getInStock() 
 {
 return inStock; 
 }
 
 public void setInStock(int newInStock) 
 {
 inStock = newInStock; 
 if (inStock < 0)
 inStock = 0; 
 return;
 }
 
 public int incrementInStock(int increment) 
 {
 setInStock(inStock + increment); 
 return inStock;
 }
 public int decrementInStock(int decrement) 
 {
 setInStock(inStock - decrement); 
 return inStock;
 }
}

We wrote a GUI class InventoryWindow to use and test this Inventory bean, which you can see on the CD-ROM. It is quite straightforward. When you run it, you will see something like Figure 14.2.


Figure 14.2: A GUI window to test the Inventory bean

There is a problem with the GUI application, however. It does not update the value displayed for "Current stock" after you make changes. You can fix this by monitoring for changes to the inStock property and reflecting the new value in the window.

Bound properties

So, a default constructor and proper use of get and set methods makes your class a bean. That's easy enough. However, if you want to go further, you can, with some additional coding using classes in the java.beans package. For example, if you want to allow other classes to monitor for changes in one of your properties, you can make that property a bound property. To do this, you need to supply a method for others to call to register their interest in being notified. You will record every interested object in an array, and when the setXXX method is called, you walk the array and call a method in each of the objects to tell it about the change. Of course, you are going to have to define an interface that callers implement, so you can define the array to be of that type, and to define the method, you will call back to when the property changes.

Luckily, much of this work is done for you. The java.beans package has an interface named PropertyChangeListener, which defines one method named propertyChanged.

Further, there is a class supplied for easily recording the references of callers who register: PropertyChangeSupport, which has two methods addPropertyChangeListener and removePropertyChangeListener. Both take an object that implements the PropertyChangeListener interface. So, to turn a property into a bound property, follow these steps:

  1. Declare an instance variable of type PropertyChangeSupport and instantiate it, passing this as the parameter.
  2. Supply methods for callers to register and un-register their interest. These should be named addPropertyChangeListener and removePropertyChangeListener, and take as a parameter an object of type PropertyChangeListener. These will call the appropriate methods in the PropertyChangeSupport object.
  3. Change the set method to call the propertyChanged method on each registered object. This is easy to do using the firePropertyChange method in the PropertyChangeSupport object. The firePropertyChange method requires you to give a name to your property, in the form of a string. This is necessary in case you have multiple bound properties in one bean; registered listeners need to be able to decide which property changed. You also have to pass both the old and the new values, as it will only call listeners if the value actually changes.

Let's put this to work by changing the Inventory class to make inStock a bound property, as shown in Listing 14.17 (only the new or changed methods are shown).

Listing 14.17: Updating the Inventory Bean to Support a Bound Property

import java.beans.*; 
public class Inventory 
{
 protected int inStock = 0; 
 protected static final int INITIAL_STOCK = 100; 
 protected PropertyChangeSupport listeners = new PropertyChangeSupport(this);
 
 public void addPropertyChangeListener(
 PropertyChangeListener listener) 
 {
 listeners.addPropertyChangeListener(listener);
 }
 public void removePropertyChangeListener( 
 PropertyChangeListener listener) 
 {
 listeners.removePropertyChangeListener(listener);
 }
 public void setInStock(int newInStock) 
 {
 int oldValue = inStock;
 inStock = newInStock; 
 if (inStock < 0) 
 inStock = 0; 
 listeners.firePropertyChange(
 "inStock",oldValue,inStock);
 return;
 }
}

Since the incrementInStock and decrementInStock methods call setInStock, as you saw in Listing 14.16, they too will fire a property-change event. Now you have a bound property, so users of this bean can call the addPropertyChangeListener method to be informed of changes to the inStock property. How are they informed? Well, to call the method they must implement the PropertyChangeListener interface and so supply a propertyChange method, which will get called when the inStock property changes. They must also call addPropertyChangeListener(this) on their Inventory object to register interest.

Their propertyChanged method defines a parameter object of type PropertyChange Event, which Java creates for you when you call the firePropertyChange method. This event object has the methods getPropertyName, getOldValue, and getNewValue. The first returns the inStock property name, while the latter two return the old and new values for that property. However, since the property is a primitive int type, getNewValue returns an Integer object. To see this in action, look at the revised InventoryWindow class in the Listing14-17 directory on the CD-ROM. In it, we coded all this so that we can update the value of the inventory displayed in the window. Here is what our propertyChange method looks like:

public void propertyChange(PropertyChangeEvent evt) 
{ 
 if (evt.getPropertyName().equals("inStock")) 
 currStock.setText(((Integer)evt.getNewValue()).toString()); 
}

The variable currStock is a non-editable JTextField part that shows the current stock level. When you run the window now, this value is kept up-to-date, as shown in Figure 14.3.


Figure 14.3: An updated GUI window that tracks changes in inventory level

Although very rarely used, bound properties can also be defined as constrained properties, which are properties whose listeners can "veto" or reject the change. These are coded using add/removeVetoableChangeListener methods and the VetoableChange Listener interface, and the VetoableChangeSupport class. Listeners reject a change by throwing an agreed-upon exception, which your bean must monitor for and handle by undoing the change.

Events

Bound properties are really a special form of events. An event is something that happens during the life of a bean, in which other classes might be interested. Like a bound property, other classes register their interest in an event and are called back when that event happens. You have complete freedom in deciding what events you might like to support. You have seen events already, in GUI programming in Chapter 12. For example, a JButton object will fire an actionPerformed event when the user clicks on the button.

Events are usually associated with GUIs, and reflect something that a user can do. They are a way to inform interested code about a user-initiated event. However, you can define events for anything. For example, let's enhance the Inventory bean to fire an event when the stock gets below a given threshold property, which defaults to 10, but can also be set by callers. To create an event from scratch like this, you must do the following:

  1. Define an interface that callers can implement, such as ThreshholdListener-Interface, which contains a method to be called when the event is fired. We name ours threshholdExceeded. By convention, all event-listener interfaces should be defined to extend the EventListener interface in java.util. This is an empty interface whose only role is to tag event interfaces so that tools can identify these interfaces as "event interfaces."
  2. Define an event class that will hold information about the event, and which an object of that event will be passed as a parameter to the interface call-back method. We name our event class ThreshholdEvent, and supply methods in it named getThreshholdValue, getOldValue, and getNewValue. By convention, all non-GUI event classes extend EventObject in package java.util and require the object that fires the event as a parameter to the constructor. This is subsequently retrievable by calling getSource.
  3. Supply addXXXListener and removeXXXListener methods in the class, which take an object that implements XXXListener, and records (or removes) that object in a list. Our methods are named addThreshholdListener and removeThresh holdListener, and they take a ThreshholdListener object as a parameter.
  4. Change the appropriate code in the bean to check for the event happening. When it does, walk the list of registered listeners and call the call-back method (threshholdExceeded) in each one. This requires the creation of an instance of the event (ThreshholdEvent) to pass to the call-back method as a parameter.

Tedious, but not too difficult. Let's start with the event class, shown in Listing 14.18.

Listing 14.18: A New Event Class for Threshold-Exceeded Events

import java.util.*;
 
public class ThreshholdEvent extends EventObject 
{
 protected int threshholdValue, oldValue, newValue;
 
 public ThreshholdEvent(Object source, int threshholdValue, 
 int oldValue, int newValue)
 {
 super(source); 
 this.threshholdValue = threshholdValue; 
 this.oldValue = oldValue; 
 this.newValue = newValue;
 } 
 public int getThreshholdValue() 
 {
 return threshholdValue; 
 } 
 public int getOldValue() 
 {
 return oldValue;
 } 
 public int getNewValue() 
 {
 return newValue; 
 }
}

Here is the new interface that listeners must implement:

public interface ThreshholdListener extends java.util.EventListener 
{
 public void threshholdExceeded(ThreshholdEvent evt); 
}

Finally, the changes to the Inventory bean class are shown in Listing 14.19 (again, only changes are shown).

Listing 14.19: The Updated Inventory Bean with Support for Threshold Events

import java.beans.*; 
import java.util.*; 
public class Inventory 
{
 // ... 
 protected int threshhold = 10; 
 protected Vector eventListeners = new Vector();
 
 public synchronized void addThreshholdListener(
 ThreshholdListener listener) 
 {
 eventListeners.addElement(listener); 
 }
 public synchronized void removeThreshholdListener( 
 PropertyChangeListener listener) 
 {
 eventListeners.removeElement(listener); 
 }
 protected void fireThreshholdEvent(int oldValue) 
 { 
 ThreshholdEvent event = new ThreshholdEvent( 
 this,oldValue,inStock,threshhold); 
 ThreshholdListener listener; 
 for (int idx=0; idx < eventListeners.size(); idx++) 
 {
 listener = (ThreshholdListener) 
 eventListeners.elementAt(idx); 
 listener.threshholdExceeded(event); 
 } 
 } 
 public int getThreshhold() 
 {
 return threshhold; 
 } 
 public void setThreshhold(int newThreshhold) 
 {
 threshhold = newThreshhold; 
 } 
 public void setInStock(int newInStock) 
 { 
 // ... same ... 
 if (inStock < threshhold) 
 fireThreshholdEvent(oldValue); 
 return;
 }

Notice the code does its own tracking of registered event-listeners, using a simple Vector. The add and remove methods simply use the addElement and removeElement methods of the Vector class. These methods had to be made synchronized to ensure they work properly in a threaded environment. The threshhold value is made into a property so that callers can query and change it. Finally, the setInStock method now has code at the end to check if the inventory is below the threshold value; if so, it fires the event. This is done by calling the helper method fireThreshholdEvent, which walks the list of { registered listeners and, for each one, calls the threshholdExceeded method dictated by the interface. A ThreshholdEvent object is created to pass to that method.

To test this, we updated the InventoryWindow class to implement the ThreshholdListener interface, call the addThreshholdListener method on the Inventory object, and code the threshholdExceeded method. In that method, we used JOptionPane.showMessageDialog to display a warning message to the user, as shown in Figure 14.4. The event we invented supports multiple listeners, which is called a multi-cast event. You could support a single listener only, which is known as a unicast event.

click to expand
Figure 14.4: The updated GUI window that issues a warning for threshold events

Serializing beans

One last thing you should do in the bean class is make it serializable. This is because many tools support the user changing a bean instances' properties by serializing the bean to disk. This is very easily done, as shown in Listing 14.20.

Listing 14.20: The Updated Inventory Bean, Now Serializable

public class Inventory implements java.io.Serializable 
{
 protected int inStock = 0; 
 protected static final int INITIAL_STOCK = 100; 
 transient protected PropertyChangeSupport listeners = 
 new PropertyChangeSupport(this); 
 protected int threshhold = 10;
 transient protected Vector eventListeners = new Vector();
 
 // ... same ...
 public void addPropertyChangeListener(
 PropertyChangeListener listener) 
 {
 if (listeners == null) 
 listeners = new PropertyChangeSupport(this); 
 listeners.addPropertyChangeListener(listener); 
 }
 public synchronized void addThreshholdListener( 
 ThreshholdListener listener) 
 {
 if (eventListeners == null) 
 eventListeners = new Vector(); 
 eventListeners.addElement(listener);
 }
}

All you have to do is implement Serializable, but the example also flags instance variables that store the property and event listeners as transient, since it makes no sense to save them between sessions. (You can't assume those listener objects will exist tomorrow or on a different system.) Because of this, though, these variables will be null after the bean is restored from disk, so you have to check for this in each of the add methods, and create new objects, if necessary.

For testing, we updated the InventoryWindow class in the Listing14-20 directory on the CD-ROM to have two new buttons: Save and Restore, as shown in Figure 14.5. These will serialize the Inventory bean to disk and restore it from disk, respectively.


Figure 14.5: The updated GUI window that supports saving and restor ing the Inventory bean

The BeanInfo class

Once you have a beautiful JavaBean created, you make it available for users. They will usually use it in a Java tool such as VisualAge for Java. These tools use a Java-supplied process called introspection, using the getBeanInfo method in the java.beans.Introspector class, to get a BeanInfo object containing information about the bean. This object includes lists of its public methods, properties, bound properties, constrained properties, and events. It deduces the property names from the get/set method names, the bound properties from the existence of add/removeProperty ChangeListener methods, the constrained properties from the existence of add/removeVetoableChangeListener methods, and events from the existence of add/removeXXXListener methods.

If you want to refine this process, you can supply your own explicit BeanInfo class, which must have the same name as your bean class, but with BeanInfo appended. This class will extend the SimpleBeanInfo class in java.beans, and you can implement as many of the methods as you want from that class. Every method shown in Table 14.2 is designed to give tools more information, which is often reflected in the tool's UI to subsequently help developers who are writing code that uses the bean.

Table 14.2: Methods in the BeanInfo Class

Method

Description

getBeanDescriptor

Returns a BeanDescriptor object, which contains two methods: getBeanClass and getCustomizerClass. The latter is a class you can supply that will be launched by tools when users double-click on your bean. This is usually a GUI window for setting the properties. Without this, tools will show a standard property-sheet editor.

getDefaultEventIndex

An index into EventSetDescriptors of the most common event. Tools can display this event more prominently.

getDefaultPropertyIndex

An index into PropertyDescriptors of the most common property. Tools can display this property more prominently.

getEventSetDescriptors

An array of EventSetDescriptor objects, one per event supported by this bean. The EventSetDescriptor class describes each event with methods like getListenerType to get the listener interface for this event.

getIcon

Returns an ImageIcon object that contains a .gif file to show for this bean when tools display it.

getMethodDescriptors

Returns an array of MethodDescriptor objects describing each method to be exposed to users. Use this if you only want to expose a subset of the public methods.

If you decide to supply a BeanInfo class, you don't have to supply all these methods. Whatever you don't supply will be queried via introspection. Clearly, a BeanInfo class is not necessary for most in-house beans, but if you intend to sell or publicly distribute a bean, it is a nice touch to supply one. However, only someone who enjoys pain would write this by hand, versus using a tool like VisualAge for Java to generate it.

Beans and tools

Let's find out what a bean looks like when used in VisualAge for Java, version 3.5.3. In Figure 14.6, we have added an instance of the bean to the Visual Composition Editor (VCE) and double-clicked on it to get the property sheet. As you can see, the properties are displayed and editable.


Figure 14.6: The VisualAge for Java property sheet for the Inventory bean

What's great about tools like VisualAge is that they make it easy to create applications by linking events from one bean to methods from another (the firing of that event causes the method to be called), or by linking the bound properties of one bean to the properties of another (changes in the source property cause changes in the target property).

For example, we re-created the test window in VisualAge for Java's VCE, and linked the actionPerformed events of the buttons to the appropriate methods in the Inventory bean, getting the parameter from the entry field. We also linked the Inventory bean's inStock property to the top entry field's text property, such that any change in the former is automatically displayed in the latter. This was all done visually, with no coding on our part, although the code produced is very similar to what we coded by hand.

Another great benefit of tools like VisualAge is the tremendous help they offer in creating and refining JavaBeans. For example, Figure 14.7 shows the BeanInfo window for the Inventory bean. Notice how easy it is to work with all the properties, events, and methods. The P, E, and M superscripts identify which is which, and the R, W, and B superscripts identify readable, writable and bound properties, respectively. You can change any attribute of a property by simply editing the values at the bottom of the page. The code is updated automatically. Also, a BeanInfo class is created for you.

click to expand
Figure 14.7: The VisualAge for Java BeanInfo window

Rather than doing all the work we did to define properties and events, we could have just used this window. For example, clicking on the P button on the toolbar launches a SmartGuide for creating a new property, as shown in Figure 14.8. You have to admit, this is much easier than coding this stuff by hand!

click to expand
Figure 14.8: Create a property SmartGuide in VisualAge for Java

More beans

To find JavaBeans, go to the VisualAge Developer Domain at www.ibm.com/software/ad/vadd. This contains many third-party beans for sale. For free beans, go to IBM's alphaworks Web site, at www.ibm.com/alphaworks and follow the links for AlphaBeans. For more technical information on JavaBeans, go to Sun's Web page dedicated to them, at www.java.sun.com/beans.

Now you know beans!



Next Steps Of Servlets, Jsps, Ejbs, And More

The world of Java is exploding at an unprecedented rate. You have been given a very brief tour of the core Java language as of JSDK 1.4.0. With this under your belt, you can move on to some of the more exciting uses of Java beyond beans, applications, and applets. This includes Java servlets, JavaServer Pages and Enterprise JavaBeans. These are not part of the Standard Edition JSDK, but rather the Enterprise Edition JSDK, also known as J2EE. You do not download this, but instead you get or buy a platform that implements it. WebSphere Application Server is one such platform. It offers the runtime classes and implementations of the runtime frameworks for these and the other components that define the J2EE.

At a minimum, servlets and JSPs are part of your future. You will need to learn them. However, they beyond the scope of this book. Please do continue your Java journey, though, and get books on the elements of J2EE. We are confident that you are now very well-positioned to pick up any Java book and easily read and understand it. Whatever you do, do not stop learning now!



Summary

This "wrap up" chapter, briefly covers the following topics:

  • Inner classes
  • Static initializers
  • The Java Native Interface
  • Java Remote Method Invocation
  • Serializing objects
  • The packages in the JSDK, Standard Edition
  • Working with local stream files and file system
  • JavaBeans

Thanks for coming along for the ride! We are big believers in Java as a single technology that can span all your information technology requirements-from personal devices to rich GUIs to mission-critical enterprise applications. We are big believers in RPG, too, especially RPG IV. However, eventually, Java will play some role in all your new applications.

Java is changing the face and impressions of OS/400-and now you will be there, putting your new Java skills and enthusiasm to work! Never forget, though, that your RPG "heritage" is your strength, and knowing both RPG and Java is your biggest asset. Now it's time to talk to your manager about that raise!





Java for RPG Programmers
Java for RPG Programmers, 2nd Edition
ISBN: 193118206X
EAN: 2147483647
Year: 2002
Pages: 191

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net