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

Last month, we started a fun project in creating a chat room applet. The overall project illustrates how to create Perl scripts which will be used as back-ends for your Java applets. The Java applets will interact with the Perl program using a standard CGI interface. While you can have a lot of fun with this lightweight chat applet, my goal for you, however, is to look beyond this little applet and ask yourself, "How else can I use this method of sending data to a Web server, and process it on the Server side?". You can, for instance, use this to make a shopping cart, maintain data without using cookies, send a transaction to Cybercash (which uses Perl Scripts), etc.

This month, we will focus on the client-side Java applet. This is what the world will see as the chat room. To effectively do this, I have chosen to create an applet object and a Panel object. The applet will simply act as a container for the Panel object. The Panel object will then be the meat of the project.

The Chat class is derived from the applet Class. This class will be called by the HTML web page, which can contain parameters that the applet will extract. The parameters will be used to set the path to the Perl program and name of the log file. The log file, which gets created and maintained by the Perl Script, will reside on the Web server. In it, we will store the last ten chat submissions in order of most recent first.

The init() method of the Chat class will use the ternary operator as a method to extract the parameters in the HTML file. Normally, I never use the ternary operator for anything since it is sort of cryptic. I use it here, however, because it lends itself to this purpose. The init() method also makes an instance of the ChatPanel object which will actually contain the chat engine. After setting the applet's layout to BorderLayout, I have added the ChatPanel object to it in its Center position (see Listing 1).

The start() and stop() methods just call the ChatPanel object's start() and stop() methods, respectively. This will be important not only for starting and stopping the chat thread, but for sending a message to the Web server that the client has entered/exited the chat room.

public void start(){
chatScreen.start();
}
public void stop(){

chatScreen.stop();
}

Finally, I have declared an accessor method to get the cgiPath, called getCGIPath(). This method will be called from within the SubmitToChatServer objects.

public String getCGIPath(){
return cgiPath;
}

ChatPanel chatScreen;

Thread runner = null;
String chatLogFileName, cgiPath;
}

The ChatPanel class is quite complex. It implements Runnable, since we will use a Thread to refresh the chat data on our screen. This object will extend Panel, which we will configure as a CardLayout container. It will contain two additional Panels, one of which will gather setup information, such as the client's desired chat name, and the other will display the actual chat messages.

To construct this object, we will pass two parameters:

  • A handle to the applet
  • A String that will specify the file name of the chat log
After initializing some instance variables, we construct the panels. To build them, I have opted to nest some additional components, such as Panels, instead of using GridBagLayout. Both the Setup and Chat panels use BorderLayout. This method adds the various AWT components needed to these panels using the standard add() method. After each Panel is constructed, we will add them to the card layout panel, which will contain them (see Listing 2).

The start method checks to see if the user has entered a name. It the user has not entered a name to chat with, it will bring the setup panel to the front and request focus for the TextField, which will be where the user will enter a name. If the user has entered a name, we will create a new SubmitToChatServer object with a message indicating that this person has entered the chat room. On the same line, we start() that object, which will, in turn, spawn a new Thread to send that data to the chat server. Each line we send to the chat server will work exactly in this way, spawning a separate thread with which to send the data. Next, we will start our chat engine by creating another thread for it, if one does not already exist, and starting it. This will, in turn, invoke this object's run() method in the thread (see Listing 3).

The stop() method will do a similar thing to what the start() method does. It will create another SubmitToChatServer object with a message indicating that the user has left the chat room and start the process of sending it in another thread. After doing so, it will stop the chat engine's thread so that we will not be constantly refreshing the screen.

public void stop(){
if(runner != null){
new SubmitToChatServer("*** "+identity+"
Has Left This Chat Room. ***",app, chatFileName).start();
runner.stop();
runner = null;
}

}

The run() method will be the heart of the chat engine. The first thing we do is request focus on the TextField, which will be used for chat entry. The next part of this method required some thought and experimentation. Since the AWT's TextArea object does not wrap text to the next line when we have reached the right side of it, I needed to develop my own text wrapping technique. In order to do this, we need to figure out the width, in pixels, of the average character in a specific font. I did this by using a FontMetrics object for the chatArea. I determined the width of a string which contains the alphabet in upper and lower case. I have even included some spaces and periods. Then, I divided the length of this string by the number of characters in it. This gives me the average width, in pixels, of the text that users may type in. I can later compare this to the width of the Text area to determine how many characters will fit on a line. Once this is done, we go into an infinite while loop, which invokes a method called refresh() and pauses the thread for the length of time specified in refresh time (see Listing 4).

As you may have guessed, the refresh() method will be used to refresh the chat data on the screen. It will be responsible for downloading the chat log file and parsing its information. First, it creates a URL object for the chat log file. Next, we create a DataInputStream object, which we will use to read its data. Since the log file stores its most recent data first, we will need to read each line and pre-pend it to the front of a temporary String, named chatData, which will be used to store the new data with the oldest data first. The first line we read (Most Recent Chat) will be stored for the next time this method is called. Each line read will need to be processed by the processText() method. This method will perform the wrapping of text if necessary. We will keep doing this on a line by line basis, until we read a line that matched the first line read from the previous iteration of this method. When finished, we will append the temporary String to the chatArea TextArea and close the stream. To force the TextArea to scroll down to the bottom, we will select the last character of text contained within it (see Listing 5).

The processText() method is responsible for word wrapping within the TextArea. It takes the unprocessed text in as a parameter and returns the processed text as a String. If the text contains fewer characters than the average characters per line, then we will simply return the text with two new-line characters appended to it. If the text contains more characters than the average characters per line, then we examine the first group of characters that fit on a line. We count backwards until we find the last instance of a space. Here, we will insert a new-line character and repeat the process on the remaining text after we pre-pend the person's name, which we extract from the text, to the beginning of the remaining text. When finished, we return the completed processed text with two new-line characters appended to the end (see Listing 6).

Since most browsers in use today do not support 1.1, I still use the 1.0 event model for applets. The handleEvent method is used for both the setup and chat panels. Here, we simply handle the action events for the buttons, as well as if the person generates an action event on a TextField (by pressing the enter key, for instance). If the target is the chatLine or the sendButton, we call the sendChat() method. If the target is the nameField or the setupButton, we need to check if they have actually entered a name. If not, we tell them to enter a name. If they have entered a name, we enter the chat room, show the chat panel and start the chat engine (see Listing 7).

The sendChat() method simply creates a new SubmitToChatServer object with the text to be sent to the Perl Script. The text is a String constructed with the identity of the person and the text extracted from the chatLine TextField. We need to start the transfer, so we invoke the start() method on this object, which will send the chat line in a new Thread. Next, we need to clear the chatLine TextField.

void sendChat(){

new SubmitToChatServer(identity+" >:
"+chatLine.getText(),app,chatFileName).start(); chatLine.setText("");

}

To finish off this class, I have written a number of accessor and mutator methods. These include:

setRefreshTime()
setIdentity()
getIdentity()
setChatLogName()

I may use these methods later if I wish to be more elaborate in my applet class (see Listing 8).

There may be a number of things you wish to do to optimize this Chat applet. For instance, you may want to move the refresh time up or down depending on your chat traffic. Remember, however, that you will be refreshing at longer or shorter intervals and each refresh will read the current log file, which stores up to twenty lines of chat. If you have heavy traffic in your chat room, more than twenty lines may have been submitted during that time period. You may want to alter the Perl Script to store more lines; however, twenty seem to be more than adequate in most situations.

In addition, I have noticed that performance is drastically improved when using ISAPI Perl with IIS on Windows NT Server. You can download this for free at http://www.activestate.com. Note: You will need both the Win32 and ISAPI versions. The ISAPI version must be installed on top of the Win32 version. You may need to edit the registry, particularly in the scriptmap section under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters\Scriptmap. Here, you would want to make sure the Perl is registered to the file extensions .pl and .cgi. The .pl should be set to C:\Perl\bin\perlIS.dll and the .cgi should be set to C:\Perl\bin\Perl.exe %s %s. If you want to use the ISAPI version, rename your Perl Script's extension to .pl and make this modification in the Java program as well, where the Perl Script is referenced.

Well, that's it. I hope you have had a lot of fun with this project. It demonstrates several exciting methods of interacting with a Web server and introduces many new possibilities for your applets.

About the Author
Joseph M. DiBella is the Senior Java Instructor and Curriculum Developer for Computer Educational Services in New York City. He also is the President of HMJ Electronics, a computer consulting company which develops software and Java-enhanced Web sites. Joe can be reached at [email protected]

	

Listing 1.
  
    public void init() {  

        super.init();  

chatLogFileName = getParameter("logfile") != null ? getParameter("logfile") :"chatlog";  
   
        cgiPath = getParameter("cgipath") != null ? getParameter("cgipath") : "cgi-bin/";  

chatScreen = new ChatPanel(this,chatLogFileName);  

        setLayout(new BorderLayout());  
        add("Center",chatScreen);  

    }  
	
Listing 2.
  
public class ChatPanel extends Panel implements Runnable{  

    ChatPanel(Applet app,String fileName) {  

        super();  
        this.app = app;  
        chatFileName = fileName;  
        //Build the Setup Panel  
        Panel setup = new Panel();  
        Panel setupNorth = new Panel();  
        Panel setupCenter = new Panel();  
        Panel setupSouth = new Panel();  
        setup.setBackground(Color.cyan);  
        setupButton = new Button("Enter Chat Room");  
        nameField = new TextField(20);  
        nameField.setBackground(Color.white);  
        setupCenter.add(new Label("Enter Name:"));  
        setupCenter.add(nameField);  
        setupCenter.add(setupButton);  
        setupNorth.setFont(new Font("TimesRoman",Font.BOLD,36));  
        setupNorth.add(new Label("Welcome to Chat"));  
        setupSouth.setFont(new Font("TimesRoman",Font.BOLD,36));  
        setupSouth.add(new Label("You Must Enter A Chat Name"));  
        setup.setLayout(new BorderLayout());  
        setup.add("North", setupNorth);  
        setup.add("Center", setupCenter);  
        setup.add("South", setupSouth);  
        //Build the Chat Panel  
        Panel chat = new Panel();  
        chat.setBackground(Color.black);  
        chat.setLayout(new BorderLayout(10,10));  
        northPanel = new Panel();  
        southPanel = new Panel();  
        northPanel.setBackground(Color.cyan);  
        northPanel.setFont(new Font("TimesRoman", Font.BOLD,24));  
        northPanel.add(new Label("Joseph DiBella's Cyber-Chatterbox"));  
        chat.add("North", northPanel);  
        chatArea = new TextArea("Welcome to Joe DiBella\'s Chat Applet\n\n");  
        chatArea.setEditable(false);  
        chatArea.setBackground(Color.cyan);  
        chatArea.setForeground(Color.black);  
        chatArea.setFont(new Font("Helvetica", Font.BOLD,14));  
        chat.add("Center",chatArea);  
        chatLine = new TextField();  
        chatLine.setBackground(Color.white);  
        sendButton = new Button("Send");  
        southPanel.setLayout(new BorderLayout(5,5));  
        southPanel.add("Center",chatLine);  
        southPanel.add("East", sendButton);  
        chat.add("South", southPanel);  

        clayout=new CardLayout();  
        setLayout(clayout);  
        add("Setup", setup);  
        add("Chat", chat);  
   

    }  

Listing 3
  
    public void start(){  
        if(identity != null){  
            new SubmitToChatServer("*** "+identity+" Has Entered This Chat Room. ***",app,chatFileName).start();  
            if(runner == null){  
                runner=new Thread(this);  
                runner.start();  
            }  
        }else{  
            clayout.show(this,"Setup");  
            nameField.requestFocus();  
        }  
    }  

Listing 4.
  
    public void run(){  

        chatLine.requestFocus();  
        // Figure out line length in pixels  
        FontMetrics fm = chatArea.getFontMetrics(chatArea.getFont());  
        aveChar = fm.stringWidth("ABCDEFGHIJKLMNOPQRSTUVWXYZ  
     abcdefghijklmnopqrstuvwxyz  .. .. ")/62;  
        aveCharsPerLine = this.size().width/aveChar;  

        // Start the chat engine  
        while(true){  
            refresh();  
            try{  
                Thread.sleep(refreshTime);  
            }catch(InterruptedException e){}  

        }  

    }  

Listing 5.
  
void refresh(){  

            try{  
                log = new URL(app.getDocumentBase(),chatFileName+".log");  

                DataInputStream din = new DataInputStream(log.openStream());  

                String tempLastLine=lastLine;  
                String in="";  
                String chatData = "";  
                boolean first = true;  
                boolean finished = false;  

                while((in=din.readLine())!=null){  

                    if(in.startsWith(lastLine)){  
                        finished = true;  
                    }  
                    if(!finished){  
                        if(first){  
                                tempLastLine=in;  
                                first=false;  
                        }  

                         chatData = processText(in)+chatData;  
                    }  

                 }  

                  chatArea.appendText(chatData);  
                  lastLine=tempLastLine;  
                  din.close();  

            }catch(Exception e){  
                System.out.println("Error in refresh:" + e.toString());  

            }  
            // Force the Text Area to scroll to the end  
            chatArea.select(chatArea.getText().  
               length(),chatArea.getText().length());  
   

    }  

Listing 6.
  
    String processText(String text){  
        /* This method is responsible for word wrapping in the text area. If a line is longer than  
           the width of the Text area, we will insert a new-line chatacter in place of a space */  
        int textLength = text.length();  
        if(textLength <= aveCharsPerLine){  
            return text+"\n\n";  
        }  
        // We will need the name of the person if we need to wrap. This extracts that info  
        String chatName = text.substring(0,text.indexOf(">:")+2);  
        String processedText = "";  

        while(text.length() > aveCharsPerLine){  
            int i = text.lastIndexOf((int)' ',aveCharsPerLine);  
            try{  
                processedText += text.substring(0,i)+"\n";  
                text = chatName + text.substring(i);  
            }catch(Exception e){/* code for exception*/  
                System.out.println("Error processing text:" + e.toString());  

            }  
        }  

        return processedText+text+"\n\n";  

    }  

Listing 7.
  
    public boolean handleEvent(Event event) {  
        if (event.id == Event.ACTION_EVENT && (event.target == sendButton||event.target == chatLine)) 
		{  
                sendChat();  
                chatLine.requestFocus();  
                return true;  
        }  
        if (event.id == Event.ACTION_EVENT && (event.target == setupButton||event.target == nameField)){  
             if(nameField.getText().equals("")){  
                app.showStatus("You must enter a name");  
                nameField.requestFocus();  

            }else{  
                app.showStatus("Entering Chat Room");  
                setIdentity(nameField.getText());  
                clayout.show(this,"Chat");  
                start();  
            }  
            return true;  
        }  

        return super.handleEvent(event);  
    }  

Listing 8.
  
    public void setRefreshTime(int i){  
        refreshTime = i;  
    }  

    public void setIdentity(String i){  
        identity = i;  

    }  
    public String getIdentity(){  
        return identity;  
    }  
    public void setChatLogName(String i){  
        chatFileName = i;  
    }  

    //{{DECLARE_CONTROLS  
    private TextArea chatArea;  
    private TextField chatLine;  
    private Panel northPanel, southPanel;  
    private Button sendButton;  
    private Applet app;  
    private int refreshTime = 10000;  
    private int aveChar, aveCharsPerLine, chatSize;  
    private String identity = null;  
    private String chatFileName = "chatlog";  
    private URL log;  
    private String lastLine=" ";  
    private Thread runner;  
    private Button setupButton;  
    private TextField nameField;  
    private CardLayout clayout;  
        //}}  
}  
  
      
 

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

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