• No results found

Vectors and Serialisation

thread1 sum thread2

4.9 Vectors and Serialisation

}

Output from this program is shown in Figure 4.6.

Figure 4.6 Outputting the contents of serialised objects stored in a Vector.

4.9 Vectors and Serialisation

It is much more efficient to save a single Vector to disc than it is to save a series of individual objects. Placing a series of objects into a single Vector is a very neat way of packaging and transferring our objects. This technique carries another significant advantage: we shall have some form of random access, via the Vector class's elementAt method (albeit based on knowing each element's position within the Vector). Without this, we have the considerable disadvantage of being restricted to serial access only.

Example

This is the same as the example in the previous section, but now using a Vector for transfer of objects to/from the file. We could use the same Vector object for sending objects out to the file and for receiving them back from the file, but two Vector objects have been used below simply to demonstrate beyond any doubt that the values have been read back in (and are not simply the original values, still held in the Vector object).

import java.io.*;

import java.util.*;

public class VectorSerialise {

public static void main(String[] args)

throws IOException, ClassNotFoundException {

ObjectOutputStream outStream =

new ObjectOutputStream(

new FileOutputStream("personnelvec.dat"));

Vector<Personnel> staffVectorOut =

new Vector<Personnel>();

Vector<Personnel> staffVectorIn =

new Vector<Personnel>();

Personnel[] staff =

{new Personnel(123456,"Smith", "John"), new Personnel(234567,"Jones", "Sally Ann"), new Personnel(999999,"Black", "James Paul")};

for (int i=0; i<staff.length; i++) staffVectorOut.add(staff[i]);

outStream.writeObject(staffVectorOut);

outStream.close();

ObjectInputStream inStream =

new ObjectInputStream(

new FileInputStream("personnelvec.dat"));

int staffCount = 0;

try {

staffVectorIn =

(Vector<Personnel>)inStream.readObject();

//The compiler will issue a warning for the //above line, but ignore this!

for (Personnel person:staffVectorIn) {

staffCount++;

System.out.println(

"\nStaff member " + staffCount);

System.out.println("Payroll number: "

+ person.getPayNum());

System.out.println("Surname: "

+ person.getSurname());

System.out.println("First names: "

+ person.getFirstNames());

}

System.out.println("\n");

}

catch (EOFException eofEx) {

System.out.println(

"\n\n*** End of file ***\n");

inStream.close();

} } }

class Personnel implements Serializable {

private long payrollNum;

private String surname;

private String firstNames;

public Personnel(long payNum,String sName,

String fNames)

public void setSurname(String sName) {

surname = sName;

} }

Using methods covered in Chapter 2, the above code may be adapted very easily to produce a simple client-server application in which the server supplies personnel details in response to client requests. The only difference is that, instead of sending a series of strings from the server to the client(s), we shall now be passing a vector.

Consequently, we shall not be making use of a PrintWriter object in our server.

Instead, we shall need to create an ObjectOutputStream object. We do this by passing the OutputStream object returned by our server's Socket object to the ObjectOutputStream constructor, instead of to the PrintWriter constructor (as was done previously).

Example

Suppose that the Socket object is called link and the output object is called out. Then, instead of

PrintWriter out =

new PrintWriter(link.getOutputStream(),true);

we shall have:

ObjectOutputStream out =

new ObjectOutputStream(link.getOutputStream());

Since both server and client need to know about the Personnel class, we shall hold this class in a separate file, in order to avoid code duplication and to allow the class's reusability by other applications. The code for the server (PersonnelServer.java), the client (PersonnelClient.java) and class Personnel is shown below. You will find that the code for the server is an amalgamation of the first half of MessageServer.java from Chapter 2 and the early part of VectorSerialise.java from this section, while the code for the client is an amalgamation of the first part of MessageClient.java (Chapter 2) and the remainder of VectorSerialise.java. As with earlier cases, this example is unrealistically simple, but serves to illustrate all the required steps of a socket-based client-server application for transmitting whole objects, without overwhelming the reader with unnecessary detail. Upon receiving the message 'SEND PERSONNEL DETAILS' from a client, the server simply transmits the vector containing the three Personnel objects used for demonstration purposes in this section and the previous one.

import java.io.*;

import java.net.*;

import java.util.*;

public class PersonnelServer {

private static ServerSocket serverSocket;

private static final int PORT = 1234;

private static Socket link;

private static Vector<Personnel> staffVectorOut;

private static Scanner inStream;

private static ObjectOutputStream outStream;

public static void main(String[] args) {

System.out.println("Opening port...\n");

try {

serverSocket = new ServerSocket(PORT);

}

catch(IOException ioEx) {

System.out.println(

"Unable to attach to port!");

System.exit(1);

}

staffVectorOut = new Vector<Personnel>();

Personnel[] staff =

{new Personnel(123456,"Smith", "John"), new Personnel(234567,"Jones", "Sally Ann"), new Personnel(999999,"Black", "James Paul")};

for (int i=0; i<staff.length; i++) staffVectorOut.add(staff[i]);

startServer();

}

private static void startServer() {

do {

try {

link = serverSocket.accept();

inStream =

new Scanner(link.getInputStream());

outStream =

new ObjectOutputStream(

link.getOutputStream());

/*

The above line and associated declaration

are the only really new code featured in

this example.

*/

String message = inStream.nextLine();

if (message.equals(

"SEND PERSONNEL DETAILS"))

{

outStream.writeObject(

staffVectorOut);

outStream.close();

}

System.out.println(

"\n* Closing connection... *");

link.close();

}

catch(IOException ioEx)

{

ioEx.printStackTrace();

}

}while (true);

} }

The only new point worthy of note in the code for the client is the necessary inclusion of throws ClassNotFoundException, both in the method that directly accesses the vector of Personnel objects (the run method) and in the method that calls this one (the main method)...

import java.io.*;

import java.net.*;

import java.util.*;

public class PersonnelClient {

private static InetAddress host;

private static final int PORT = 1234;

public static void main(String[] args)

throws ClassNotFoundException

{

try {

host = InetAddress.getLocalHost();

}

catch(UnknownHostException uhEx) {

System.out.println("Host ID not found!");

System.exit(1);

}

talkToServer();

}

private static void talkToServer()

throws ClassNotFoundException

{

try {

Socket link = new Socket(host,PORT);

ObjectInputStream inStream =

new ObjectInputStream(

link.getInputStream());

PrintWriter outStream =

new PrintWriter(

link.getOutputStream(),true);

//Set up stream for keyboard entry...

Scanner userEntry = new Scanner(System.in);

outStream.println("SEND PERSONNEL DETAILS");

Vector<Personnel> response =

(Vector<Personnel>)inStream.readObject();

/*

As in VectorSerialise, the compiler will issue a warning for the line above.

Simply ignore this warning.

*/

System.out.println(

"\n* Closing connection... *");

link.close();

int staffCount = 0;

for (Personnel person:response) {

staffCount++;

System.out.println(

"\nStaff member " + staffCount);

System.out.println("Payroll number: "

+ person.getPayNum());

System.out.println("Surname: "

+ person.getSurname());

System.out.println("First names: "

+ person.getFirstNames());

}

System.out.println("\n\n");

}

catch(IOException ioEx) {

ioEx.printStackTrace();

} } }

Finally, the code for the Personnel class...

class Personnel implements java.io.Serializable {

private long payrollNum;

private String surname;

private String firstNames;

public Personnel(long payNum,String sName,

String fNames)

{

payrollNum = payNum;

surname = sName;

firstNames = fNames;

}

public long getPayNum() {

return payrollNum;

}

public String getSurname() {

return surname;

}

public String getFirstNames() {

return firstNames;

}

public void setSurname(String sName)

{

surname = sName;

} }

Figure 4.7 shows a client accessing the server, while Figure 4.8 shows the corresponding output at the server end.

Figure 4.7 Client using ObjectInputStreams to retrieve 'Vectorised' data from a server.

Fig. 4.8 Server providing 'Vectorised' data to client in preceding screenshot.

Exercises

4.1 Using a text editor or wordprocessor, create a text file holding a series of surnames and payroll numbers (at least five of each). For example:

Smith 123456 Jones 987654 Jenkins 555555 … …

Now write a Java program that uses a while loop to read values from the above text file and displays then in a table under the headings 'Surname' and 'Payroll No.'. (Don't be too concerned about precise alignment of the columns.)

4.2 Take a copy of the above program, rename the class and modify the appropriate line so that the program accepts input from the standard input stream (i.e., using a Scanner around the standard input stream, System.in). Then use redirection to feed the values from your payroll text file into your program (displaying the contents as before).

4.3 Write a (very short) program that creates a serial text file holding just two or three names of your own choosing. After compiling and running the program, use the MS-DOS command type (or the equivalent command for your platform) to display the file's contents. For example:

type names.txt

4.4 Using a text editor or word processor, create a text file containing a series of five surnames and examination marks, each item on a separate line. For example:

Smith 47 Jones 63

By extending the code given below, create a random access file called results.dat, accepting input from the standard input stream (via a Scanner object) and redirecting input from the above text file. Each record should comprise a student surname and examination mark. When all records have been

written, reposition the file pointer to the start of the file and then read each record in turn, displaying its contents on the screen.

import java.io.*;

import java.util.*;

public class FileResults {

private static final long REC_SIZE = 34;

private static final int SURNAME_SIZE = 15;

private static String surname;

private static int mark;

public static void main(String[] args)

throws IOException

public static void writeString(

RandomAccessFile file, String text, int fixedSize) throws IOException {

public static String readString(

RandomAccessFile file, int fixedSize) throws IOException {

4.5 Making use of class Results shown below, repeat the above program, this time writing/reading objects of class Result. (When displaying the names and marks that have been read, of course, you must make use of the methods of class Result.) Once again, redirect initial input to come from your text file.

class Result implements Serializable {

private String surname;

private int mark;

public Result(String name, int score) {

surname = name;

mark = score;

}

public String getName() {

return surname;

}

public void setName(String name) {

surname = name;

}

public int getMark() {

return mark;

}

public void setMark(int score) {

if ((score>=0) && (score<=100)) mark = score;

} }

4.6 Using class Personnel from Section 4.9, create a simple GUI-based program called ChooseSaveFile.java that has no components, but creates an instance of itself within main and has the usual window-closing code (also within main).

Within the constructor for the class, declare and initialise an array of three Personnel objects (as in program VectorSerialise.java from Section 4.9) and write the objects from the array to a file (using an ObjectOutputStream). The name and location of the file should be chosen by the user via a JFileChooser object. Note that you will need to close down the (empty) application window by clicking on the window close box.

4.7 Take a copy of the above program, rename it ReadFile.java and modify the code to make use of a JFileChooser object that allows a file to be selected for reading. Use the JFileChooser object to read from the file created above and get your program to use method getSurname of class Personnel to display the surnames of all the staff whose details were saved.

Learning Objectives

After reading this chapter, you should:

• understand the fundamental purpose of RMI;

• understand how RMI works;

• be able to implement an RMI client/server application involving .class files that are available locally;

• appreciate the potential danger presented by .class files downloaded from remote locations;

• know how security managers may be used to overcome the above danger.

With all our method calls so far, the objects upon which such methods have been invoked have been local. However, in a distributed environment, it is often desirable to be able to invoke methods on remote objects (i.e., on objects located on other systems). RMI (Remote Method Invocation) provides a platform-independent means of doing just this. Under RMI, the networking details required by explicit programming of streams and sockets disappear and the fact that an object is located remotely is almost transparent to the Java programmer. Once a reference to the remote object has been obtained, the methods of that object may be invoked in exactly the same way as those of local objects. Behind the scenes, of course, RMI will be making use of byte streams to transfer data and method invocations, but all of this is handled automatically by the RMI infrastructure. RMI has been a core component of Java from the earliest release of the language, but has undergone some evolutionary changes since its original specification.