CPSC 124, Fall 2005
Lab 10: Components and Networking

As usual, you can begin the lab by creating a new Java project in Eclipse. Import the following files from the directory /classes/f05/cs124/lab10: Chat.java, ChatPanel.java, SimpleNet.java, and SimpleNetObserver.java. The main program is Chat.java. You can run this program now. It will open a window that shows a large white "transcript" area where messages can be displayed, a text-input box where the user can type, and a "Send" button. If you type something in the input box and click the "Send" button, the message that you typed is posted to the transcript. In fact, you don't actually have to press the "Send" button -- you can just press return while typing in the input box. (The button is really just there for people who aren't smart enough to figure this out!)

In the first part of the lab, you will add another row of buttons to the top of the window and program some responses to occur when the buttons are clicked. In the second part of the lab, you will added a networking capability to the window, so that it can be used for a simple network chat between two people. There is also a third part, in which you will manage the interface better to reflect the state of the program by, for example, disabling buttons when it would not make sense to click them. Since much of this is new material, this lab will be a little more tutorial in nature than usual. You actually don't have to think too much until you get to Part 3.

Note that there is no applet version of the program in this lab, since the type of networking used by the program is not permitted in applets. This restriction is for security -- since applets can be downloaded from any site on the Web, there are certain things that they should not be trusted to do. Opening arbitrary network connections of one of these things.

Before you begin, you can try the completed program. It can be found in the file ChatComplete.jar in the directory /classes/f05/cs124. To use this program, you must run two copies of it on different computers (although, in fact, you can run them on the same computer if you want). Find a partner in the class to work with. Both people should run the program. To make a connection, one program must first "listen" for a connection, and the other program must "connect" to the listener. So, one person of the pair should click the "Listen" button; this makes the program listen for a connection on "port 12321". (The port number is an arbitrary number in the range 1024 to 65535 that identifies the listener.)

The second person needs to know the computer name of the machine where the first program is running. The name is posted on the computer and is something like "gul017" for the computers in Gulick 208. You can also find the computer name by using the hostname in a command-line window. Once you have this information, click the "Connect" button in the second copy of the program. You will be asked to enter the name of the computer to which you want to connect. Once the connection has been opened, you can send messages back and forth. Try it. Either side can close the connection by clicking the "Close" button. The connection is also closed if one of the windows is closed.


Part 1: Building Interfaces with GUI Components

GUI interfaces are made up of components such as buttons, text-input boxes, and menus. In Java, a JPanel is a component that can hold and organize other components. The ChatPanel class defines the main panel of the chat program, which will hold all the other components. You will be doing all your work in the file ChatPanel.java that defines this class.

The starter version of the program actually uses another panel. It's the gray area on the bottom of the window that holds the text-input box and the Send button. Using panels within panels is a typical way of organizing the components in a window. In the first part of the lab, you will add yet another panel. The panel will go at the top of the window and will contain five buttons. The buttons will be labeled "Listen", "Connect", "Close", "Clear, and "Quit". To do this:

  1. Declare five instance variables to represent the five buttons. You have to use instance variables, not local variables, since the buttons will be created in the constructor and will also be used in the actionPerformed() method. The instance variable for the send button is already declared as: JButton sendButton;
  2. Create the actual button objects. Remember that declaring a variable does not create an object; for that, you need to use the new operator. Create the buttons in the constructor of the ChatPanel class. When you call the constructor for a JButton, you can provide the label of the button as a parameter. The Send button has already been created as: sendButton = new JButton("Send")
  3. For a button to do anything, you have to add an ActionListener to it. Since the ChatPanel is itself an ActionListener, you can use the special variable this, referring to the ChatPanel object itself, as the ActionListener. Add this as an ActionListener for each button. This has already been done for the Send button with the command: sendButton.addActionListener(this)
  4. The five buttons must be added to a panel that is then added to the ChatPanel. For this, you can imitate what is done for the bottom panel:
         JPanel bottom = new JPanel(); // Create the bottom panel.
         bottom.setBackground(Color.GRAY);
         bottom.add(inputBox);    // Add the input box to the bottom panel.
         bottom.add(sendButton);  // Add the send button to the bottom panel.
         add(bottom, BorderLayout.SOUTH);  // Add bottom to the main ChatPanel.
    
    That is, create a "top" panel, and add each of the five buttons to the top panel. Then use add(top,BorderLayout.NORTH) to add the top panel to the main ChatPanel. (When you use "add" rather than "top.add" or "bottom.add", you are calling the add method in the ChatPanel class, so you are adding something to the ChatPanel itself. The parameter BorderLayout.NORTH is a constant that specifies the position of the thing that you are adding.)
  5. Add code to the actionPerformed() method to respond when each of the buttons is clicked. When the "Quit" button is clicked, you should call System.exit() to terminate the program. When the "Clear" button is clicked, you should remove all the text from the transcript; you can do this by calling transcript.setText(""). The other three buttons are related to networking. For now, you might want to post a message to the transcript by calling, for example postMessage("Clicked Listen"), so that you can make sure that the button is really working.

That's all there is to it. Although there are a lot more details still to be covered, you can already see the basics of creating GUI's: Create components, lay them out in a window, "wire" the event-handling by defining listeners, and program responses to the events.

You should be able to test your program to make sure that all the buttons are there and that the events actually get through to the actionPerformed() method.


Part 2: Simple Networking

A computer network is a very complicated thing, and writing programs for networked computing has never been easy. Java does a good job of providing a reasonably easy-to-use API for network programming, but it is still not trivial. The hardest thing to deal with is the fundamentally asynchronous nature of network events. Writing robust code that can handle networking errors is also a problem. The SimpleNet class is my attempt to provide a simplified network API that hides a lot of the difficulty.

A SimpleNet object represents one end of a two-way network connection. It assumes that the messages that are sent over the connection are lines of text. It includes methods for opening and closing a connection and for sending messages. Messages coming from the other side of the connection are asynchronous; that is, they arrive at unpredictable times. To handle incoming messages and other asynchronous network events, the SimpleNet class works with an interface named SimpleNetObserver. This interface defines several methods that will be called by the SimpleNet object when various network-related events occur. A SimpleNet object must have an "observer" that implements the SimpleNetObserver interface. The actual implementations of the event-handling methods are in the observer object -- the SimpleNet object is only responsible for detecting the event and calling the appropriate method in the observer.

In the chat program that you are writing, the ChatPanel object will be the observer object. This means that the ChatPanel class must implement the SimpleNetObserver interface. That is, you have to (1) declare that the ChatPanel class implements the interface, and (2) add definitions of the methods specified in the interface to the class. You will also have to create a SimpleNet object to manage the network connection. Here are the steps in detail:

  1. Declare that the ChatPanel class implements the SimpleNetObserver interface by changing the first line of the class definition to:
         public class ChatPanel extends JPanel implements ActionListener, SimpleNetObserver { 
    
  2. When you do step one, Eclipse will mark the line as an error. This is because the ChatPanel does not actually implement the methods specified in the SimpleNetObserver interface. If you click the error icon at the left end of the line, Eclipse will present you with a list of possible actions. Double-click "Add unimplemented methods". Eclipse will add the method declarations to the ChatPanel class for you; you will just have to fill in their bodies. (You will also have to erase the junk comments that it sticks in.) The methods are:
        public void connectionOpened(SimpleNet connection);
        public void connectionDataReceived(SimpleNet connection, String data);
        public void connectionClosed(SimpleNet connection);
        public void connectionClosedByPeer(SimpleNet connection);
        public void connectionClosedWithError(SimpleNet connection, String errorMessage);
    
    Of course, you could also add the method definitions by hand. Note that the "connection" parameter in each method tells which SimpleNet object is calling the method. Since you are will only be using one SimpleNet object, you can ignore this parameter when you write the method bodies. The meaning of each method should be reasonabley clear, but you can look at the file SimpleNetObserver.java for more information if you want.
  3. The next thing that you need is a SimpleNet object to manage your network connections. Add an instance variable named connection of type SimpleNet to your ChatPanel class. In the ChatPanel constructor, create the object with connection = new SimpleNet(this). The parameter this represents the ChatPanel itself. It tells the SimpleNet object that its "observer" is the ChatPanel; this means that when network events occur, the methods in your ChatPanel class will be called.
  4. To do the actual network programming, you have to program appropriate responses to button clicks in the actionPerformed() method, and you have to program appropriate responses to network events in the methods from the SimpleNetObserver interface. The "Listen" button should make the program wait for a connection. You can do this with connection.listen(12321). The "12321" is the port number. (You could use a different port number, as long as the other party to the connection has the correct number.)
  5. To program a response to the "Connect" button, you have to know which computer to connect to. You probably don't want to hard-code a specific computer name into the program, so you can ask the user for the computer name. This is a GUI program, so you can't use System.out or TextIO. Fortunately, Java has a simple GUI method for asking the user for some input. You can say:
        String host = JOptionPane.showInputDialog(this,
                           "Enter the name or IP number of the computer\n" 
                           + "that you want to connnect to.");
    
    This will display a message box where the user can type a response. The user can cancel the message box instead of replying. In that case, the return value will be null and you should not do anything. If the return value is not null, you can say "connection.connect(host,12321)" to open the connection.
  6. In response to the "Close" button, you can simply tell sthe the SimpleNet object to close the connection with connection.close().
  7. The "Send" button should send the contents of the text input box as a message to the other side of the network connection. You can send a String str as a message with connection.send(str)
  8. In your response to each of the buttons, in addition to performing some network action, it's a good idea to inform the user about what is going on by putting a message up on the transcript. You can use postMessage(str) to add a String str to the transcript. This is already done for the "Send" button. (An extra carriage return is added automatically at the end, but you can include extra \n characters in the string if you want.) Add an appropriate message to the user to each button response.
  9. Next, you want to move on to the implementation of the SimpleNetObserver methods. For now, you can simply program each method to post an appropriate message to the transcript. Of course in the connectionDataReceived() method, you want to post the message that has been received so that the user can see it! Also, in the connectionClosedWithError() method, you probably want to tell the user what the error was; that is, you want to include the errorMessage parameter in the message that you post to the transcript.

If you've done all this correctly, you should have a working program. Here are some things you can try. (If you encounter any problems that you can't fix, ask for help!)

Try to connect to a non-existent computer. Click the "Connect" button. When asked for a host name, enter, for example, "foo.bar". You should get an Unknown Host error. Note that calling connection.connect() actually just initiated a connection attempt. When that attempt failed failed because of the error, the SimpleNet object called the connectionClosedWithError() method in your class.

Tell your program to "Listen". You can then get a partner to try connecting. Alternatively, you can open another copy of your program and tell that one to "Connect" -- enter the name of the computer that you are working on as the host name. The connection should open, and you should see the message that you programmed in the connectionOpened() method. Once the connection is opened, you can enter a message in the text input box and "Send" it. The message should appear in the transcript of both the sender and receiver. When either party clicks "Close" (or closes the window), you should see appropriate messages in the transcript.


Part 3: Managing State

Although you now have a working program, it really only works if the user does things in the right order. There is nothing to stop a user from trying to send a message even if a connection is not open or from trying to open a connection when one is already open. To fix this problem, you should disable certain parts of the GUI when their functions are not appropriate. For example, by disabling the "Connect" and "Listen" buttons while a connection is open, you make it impossible for the user to try to open a second connection until the first one is closed. The appearance of the button changes when it is disabled, so you are also giving visual feedback to the user about the state of the program. In the third part of the lab, you will manage the visual interface to reflect the state of the program.

The initial state is established in the ChatPanel constructor. When the object is first constructed, there is no connection, so it doesn't make sense for the "Close" or "Send" buttons to be enabled. You can disable them by adding the lines

     closeButton.setEnabled(false);
     sendButton.setEnabled(false);

to the constructor. In addition, you want to stop the user from typing into the text input box. You can do this with the command

     inputBox.setEditable(false);

The input box, close button, and send button should be re-activated when a connection is opened. Be careful! You don't want to do it when the user requests the connection but when the connection is actually opened. You know the connection is opened when the connectionOpened() method is called, so that's where you should re-activate the buttons and input box (by calling setEnabled and setEditable with parameter true instead of false).

You also have to worry about enabling and disabling the "Listen" and "Connect" buttons at appropriate times. And you need to de-activate the "Send" button, "Close" button, and input box when a connection is closed. You should complete your program by adding the appropriate commands to the actionPerformed() method and to the SimpleNetObserver methods.


David J. Eck, November 2005