Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id NAA21754; Tue, 19 Nov 2002 13:49:49 -0500 (EST) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id NAA23967 for ; Tue, 19 Nov 2002 13:49:46 -0500 (EST) Date: 19 Nov 2002 08:57:30 -0800 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <25700394-1301180217@hermes.sun.com> Subject: Core Java Technologies Tech Tips, Nov. 19, 2002 (Multi-Column Lists, Timeouts on Socket Connections) Mime-Version: 1.0 Content-Type: text/html; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 Core Java Technologies Technical Tips
image
image
Core Java Technologies Technical Tips
image
   View this issue as simple text November 19, 2002    

In this Issue

Welcome to the Core JavaTM Technologies Tech Tips, November 19, 2002. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SETM).

This issue covers:

Displaying Multi-column Lists
Dealing with Timeouts on Socket Connections

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

Pixel

DISPLAYING MULTI-COLUMN LISTS

A common request in newsgroups and forums lately seems to be the ability to have a JList show its options in multiple columns. In this scenario, the columns are displayed in the JList, and then a user selects a row in the display. There are at least three ways to satisfy this request. This tip examines each of these three approaches.

First, let's examine what might be the simplest solution: use JTable instead of JList. The JTable component was specifically designed to offer support for multiple columns of data. So why bother "shoe-horning" into a JList what the JTable component gives you by design?

Here's an illustration of the JTable approach. The following program displays a list of countries and their current political leader in multiple columns. You can then select a row from the display. Notice that the columns have headers -- this is a nice feature of JTable. Notice too that you can display multiple rows in the JTable.

  import java.awt.*;
  import javax.swing.*;
  import javax.swing.table.*;
  
  public class Leaders {
    public static void main(String args[]) {
      JFrame frame = new JFrame("Leaders");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container contentPane = frame.getContentPane();
      String headers[] = {"Leader", "Country"};
      String data[][] = {
        {"Tony Blair", "England"},
        {"Thabo Mbeki", "South Africa"},
        {"Megawati Soekarnoputri", "Indonesia"},
        {"Hosni Mubarak", "Egypt"},
        {"Vladimir Putin", "Russia"},
        {"Vicente Fox", "Mexico"},
        {"Ariel Sharon", "Israel"}
      };
      TableModel model =
        new DefaultTableModel(data, headers) {
          // Make read-only
          public boolean isCellEditable(int x, int y) {
            return false;
          }
        };
      JTable table = new JTable(model);
      // Set selection to first row
      ListSelectionModel selectionModel =
        table.getSelectionModel();
      selectionModel.setSelectionInterval(0, 0);
      selectionModel.setSelectionMode(
        ListSelectionModel.SINGLE_SELECTION);
      // Add to screen so scrollable
      JScrollPane scrollPane = new JScrollPane (table);
      contentPane.add(scrollPane, BorderLayout.CENTER);
      frame.setSize(300, 100);
      frame.show();
    }
  }

When you use a JTable as shown in this example, you must use a ListSelectionListener to listen for the selection of a new row. Notice that, as you can do for JList, you can place the JTable in a JScrollPane and provide scrolling support.

The second way of providing multiple columns in a selectable set of data does use the JList component. For this technique, you need to think about the underlying design of the JList component. Each selectable option in a JList is not a component by itself. Instead each option is a product of what's called a renderer. A renderer knows how to draw each option in the JList but has no concept of the overall list.

Typically, a renderer is a JLabel (see the documentation of the javax.swing.DefaultListCellRenderer class. However, the ListCellRenderer interface only requires that the renderer be a Component.

     public Component getListCellRendererComponent(
         JList list,
         Object value,
         int index,
         boolean isSelected,
         boolean cellHasFocus)

Any component will do.

This suggests the second way of offering multiple columns: return a Container, where the Container contains one Component per column. Here's an example that illustrates the second approach. The example uses the same data that was used in the previous, JTable example. But in this example the data is converted to a JList model. The data is then added in the renderer.

  import java.awt.*;
  import javax.swing.*;
  
  public class ListLeaders {
    static class MyCellRenderer extends JPanel 
          implements ListCellRenderer {
      JLabel left;
      JLabel right;
      MyCellRenderer() {
        setLayout(new GridLayout(1, 2));
        left = new JLabel();
        right = new JLabel();
        left.setOpaque(true);
        right.setOpaque(true);
        add(left);
        add(right);
      }
      public Component getListCellRendererComponent(
               JList list,
               Object value,
               int index,
               boolean isSelected,
               boolean cellHasFocus) {
        String leftData = ((String[])value)[0];
        String rightData = ((String[])value)[1];
        left.setText(leftData);
        right.setText(rightData);
        if (isSelected) {
          right.setBackground(
             list.getSelectionBackground());
          right.setForeground(
             list.getSelectionForeground());
          left.setBackground(
             list.getSelectionBackground());
          left.setForeground(
             list.getSelectionForeground());
        } else {
          right.setBackground(list.getBackground());
          right.setForeground(list.getForeground());
          left.setBackground(list.getBackground());
          left.setForeground(list.getForeground());
        }
        setEnabled(list.isEnabled());
        setFont(list.getFont());
        return this;
      }
    }

    public static void main(String args[]) {
      JFrame frame = new JFrame("ListLeaders");
      frame.setDefaultCloseOperation(
        JFrame.EXIT_ON_CLOSE);
      Container contentPane = frame.getContentPane();
      String data[][] = {
        {"Tony Blair", "England"},
        {"Thabo Mbeki", "South Africa"},
        {"Megawati Soekarnoputri", "Indonesia"},
        {"Hosni Mubarak", "Egypt"},
        {"Vladimir Putin", "Russia"},
        {"Vicente Fox", "Mexico"},
        {"Ariel Sharon", "Israel"}
      };
      JList list = new JList(data);
      list.setCellRenderer(new MyCellRenderer());
      JScrollPane scrollPane = new JScrollPane(list);
      contentPane.add(scrollPane, BorderLayout.CENTER);
      frame.setSize(300, 150);
      frame.show();
    }
  }

Although the JList does provide multiple columns of data to choose from, it doesn't display grid lines (something that the JTable does provide).

The third way of getting multiple columns of data in a JList depends on a JList feature that is new in J2SE 1.4. Using this technique doesn't actually provide multiple columns of data for selection. Instead, it offers a single column of data spread over multiple columns.

This new feature is controlled by the new setLayoutOrientation method of JList. By specifying one of three modes found in JList: VERTICAL (the default), HORIZONTAL_WRAP, and VERTICAL_WRAP, you can position choices for selection in one of three different ways.

VERTICAL positions data in a single column:

  1
  2
  3
  4
  5
  6

HORIZONTAL_WRAP positions data in cells similar to the way the GridLayout layout manager positions data, that is, a row at a time:

  1   2
  3   4
  5   6

VERTICAL_WRAP positions the data in cells going up and down instead of left-to-right.

   1   4
   2   5
   3   6

Here's a demonstration of the different modes. The following program provides a geography test. It asks for the capital of a country, and lists possible answers alphabetically in three different JList components, one JList component for each of the modes. Pick an answer to see how well you know world capitals.

  import javax.swing.*;
  import javax.swing.event.*;
  import java.awt.*;
  import java.awt.event.*;

  public class ListWrap extends JFrame {

    static final String asCity[] = {
       "Asmara",     "Berlin",     "Kuala Lumpur",
       "Paramaribo", "Paris",      "Sana",
       "Sucre",      "Tokyo",      "Ulan Bator",
       "Valletta",   "Washington", "Yaounde"
       };

    static final String asCountry[] = {
       "Eritrea",  "Germany", "Malaysia",
       "Suriname", "France",  "Yemen",
       "Bolivia",  "Japan",   "Mongolia",
       "Malta",    "US",      "Cameroon"
       };

    int current;
    JTextField input;
    JLabel where;
    JButton submitButton, nextButton;

    public ListWrap() {
      setTitle( "Capitals" );
      setDefaultCloseOperation(EXIT_ON_CLOSE);

      JPanel westPanel = new JPanel(
          new GridLayout(3, 1));
      westPanel.add(new JLabel(
          "What is the capitol of...",
          JLabel.CENTER));
      westPanel.add(where =
          new JLabel(asCountry[0], JLabel.CENTER));
      input = new JTextField(10);
      JPanel westSouth = new JPanel();
      westSouth.add(input);
      westPanel.add(westSouth);

      ListSelectionListener selectionListener =
          new ListSelectionListener() {
        public void valueChanged(
            ListSelectionEvent lse) {
          if (!lse.getValueIsAdjusting()) {
            Object source = lse.getSource();
            int index = (
                (JList)source).getSelectedIndex();
            input.setText(asCity[index]);
            checkAnswer();
          }
        }
      };

      JTabbedPane tabbedPane = new JTabbedPane();

      JList normalWrap = new JList(asCity);
      normalWrap.addListSelectionListener(
          selectionListener);
      tabbedPane.addTab("Standard", null,
        new JScrollPane(
            normalWrap), "Standard Orientation");

      JList verticalWrap = new JList(asCity);
      verticalWrap.setLayoutOrientation(
          JList.VERTICAL_WRAP);
      verticalWrap.addListSelectionListener(
          selectionListener);
      tabbedPane.addTab("Vertical Wrap", null,
        verticalWrap, "Vertical Wrap Orientation");

      JList horizontalWrap = new JList(asCity);
      horizontalWrap.setLayoutOrientation(
          JList.HORIZONTAL_WRAP);
      horizontalWrap.addListSelectionListener(
          selectionListener);
      tabbedPane.addTab("Horizontal Wrap", null,
        horizontalWrap, "Horizontal Wrap Orientation");

      JPanel eastPanel = new JPanel();
      eastPanel.add(tabbedPane);

      ActionListener actionListener = 
          new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
          Object source = ae.getSource();
          if (source == submitButton) {
            checkAnswer();
          } else if (source == nextButton) {
            doNext();
          }
        }
      };

      JPanel southPanel = new JPanel();

      submitButton = new JButton("Submit Answer");
      submitButton.addActionListener(actionListener);
      southPanel.add(submitButton);

      nextButton = new JButton("Next");
      nextButton.addActionListener(actionListener);
      southPanel.add(nextButton);

      Container contentPane = getContentPane();
      contentPane.add(westPanel, BorderLayout.WEST);
      contentPane.add(eastPanel, BorderLayout.EAST);
      contentPane.add(southPanel, BorderLayout.SOUTH);

      pack();
      show();
    }

    public void checkAnswer() {
      int messageType = 0;
      String result = null;
      String s = input.getText();
    
      if (s.equalsIgnoreCase(asCity[current])) {
        result = "Correct!";
        messageType = JOptionPane.INFORMATION_MESSAGE;
      } else {
        result = "Try Again.";
        messageType = JOptionPane.WARNING_MESSAGE;
      }

      JOptionPane.showMessageDialog(this,
        result, "Result", messageType);
    }

    public void doNext() {
      current++;
      if (current >= asCountry.length) { 
        current = 0;
      }
      where.setText(asCountry[current]);
      input.setText("");
    }
 
    public static void main(String args[]) {
      new ListWrap();
    }
  }  

The number of rows and columns shown in a JList is controlled by the available screen space and the visibleRowCount property. The visibleRowCount property is an integer that specifies the preferred number of visible rows. By default, visibleRowCount has a setting of 8. If the visibleRowCount property is set to zero or a negative number, and the layoutOrientation is HORIZONTAL_WRAP, the width of the JList determines the number of rows to show. If the visibleRowCount property is set to zero or a negative number, and the layoutOrientation is VERTICAL_WRAP, the height of the JList determines the number of rows to show.

For more information on what's new in JList for J2SE 1.4, see the JList section in "Swing Changes and New Features for Java 2 SDK, Standard Edition, v 1.4".

Pixel

DEALING WITH TIMEOUTS ON SOCKET CONNECTIONS

The 1.4 version of the Java 2 Software Development Kit (SDK) incorporates many long awaited features into the standard java.net library set. Some of these features are forward thinking, such as bringing next-generation Internet Protocol version 6 (IPv6) support to TCP and UDP-based programs. Other features simply bring capabilities available to C/C++ programs for many years.

One of the new features has to do with socket timeouts. If you've been programming with the networking libraries for some time, you might ask, "Don't the libraries already support timeout?" And, technically speaking, they do. However, now with the 1.4 release, there is support for a second form of socket timeouts.

The timeout support previously available with the java.net.Socket class (that is, since the 1.1 release of the networking libraries) is through the setSoTimeout method. Through this method you can enable (or disable) the SO_TIMEOUT setting in milliseconds. This setting controls how long your read call from the socket will wait before it gives up. If you don't set the option, the call could block for a very long time, theoretically forever. By setting the SO_TIMEOUT option, you limit the amount of time a read request from across a socket can wait. If no response comes back within the specified time, the read operation gives up.

The new support for timeouts available with the 1.4 release has to do with connect timeouts. It controls how long a request waits for the server to respond with a valid connection. Servers can hold requests without processing until current requests are completed. Setting a timeout on a connection request permits you to cycle through a series of servers until one is available to handle your request. This new way of timeout is done on the connect call.

Here's a fragment of code that let's you set the connect timeout Instead of passing the InetAddress (host) and port to the Socket construction, you create a SocketAddress to identify the connection point. The timeout is then set after creating the Socket and calling its connect method, passing in the SocketAddress and the timeout value.

  int timeout = 500; // half a second
  SocketAddress socketAddress = 
    new InetSocketAddress(host, port);
  Socket socket = new Socket();
  socket.connect(socketAddress, timeout);

You'll then be connected -- assuming no exceptions are thrown. If, however, the request doesn't connect within the specified time, you'll get a SocketTimeoutException. Of course, if the server rejects the request, you'll still get a ConnectionException with a message of connection refused.

The following program demonstrates the differences in the timeout support. There are five ways to run the program:

  • Run with no parameters, that is:
    java EchoClientTest 
    
    The program tries to connect to port 7, that is, the ECHO service, on your local machine. If it isn't running, you get a connection refused message. If it is running, the program connects to the ECHO service. For each line that you type, the program displays the response from the server. That response is exactly what was sent, hence the name ECHO service.

  • Run with one parameter, for example:
    java EchoClientTest web.mit.edu 
    
    The program tries to connect to port 7 on whatever host you specify. If you have an ECHO service running inside your firewall, this option would be how you specify it. Or, if you don't have an ECHO service running that you are aware of, you can connect to the one at Massachusetts Institute of Technology at their web.mit.edu server. Of course, the point of the exercise isn't to find an existing one, but to connect to one that will timeout.

  • Run with two parameters, for example:
    java EchoClientTest web.mit.edu 9000
    
    The program uses the first parameter as the host. The second parameter then serves as an alternate port for connection. Unless you pick a well known port like 80 (which is for HTTP/web requests), the connection request should be rejected, and you'll get a Connection exception.

  • Run with three parameters, for example:
    java EchoClientTest web.mit.edu 7 5000 
    
    This tries out the new 1.4 way of setting the connect timeout value. The third parameter is the timeout setting in miliseconds. Try not to set too small a value because it won't give the receiving end a chance to accept the request. Try to find a value that is reasonable for your situation. If the server doesn't respond with a valid connection within the specified time, the program continues by throwing a SocketTimeoutException from which you can recover.

    For example, the command:
    java EchoClientTest web.mit.edu 7 5000
    
    specifies a 5 second timeout. Assuming that the server is up and running at MIT, the connection will likely be established. If so, anything you type is echoed back until you type quit. Typing quit causes the program to quit (after the echo).

    By comparison, if you enter the command:
    java EchoClientTest web.mit.edu 7 1
    
    the connect timeout is set so low that the connection won't likely happen. In this case, you get the timeout exception.

  • Run the program with four parameters, for example:
    java EchoClientTest web.mit.edu 9000 1 x
    
    The fourth parameter can be anything, it is just meant to serve as a flag. The program will use the setSoTimeout method to set the socket's read timeout setting. This setting has no effect when making a connection, so you'll always get a ConnectionException if the server doesn't connect you in time. You'll also notice that the timeout value is essentially ignored at connection time. Remember, it only affects read operations after the connection is already established.
import java.io.*;
import java.net.*;
import java.util.*;

public class EchoClientTest {

  BufferedReader reader;
  PrintWriter writer;
  int port;
  int timeout;
  Socket socket;
  SocketAddress socketAddress;
  String host;

  private static final int ECHO_PORT = 7;

  public EchoClientTest() {
    this("localhost", ECHO_PORT);
  }

  public EchoClientTest(String host) {
    this(host, ECHO_PORT);
  }
  
  public EchoClientTest(String host, int port) {
    this.host = host;
    this.port = port;
    if (getSocketNoTimeout()) {
      doWork();
    }
  }


  public EchoClientTest(
      String host, int port, int timeout) {
    this.host = host;
    this.port = port;
    this.timeout = timeout;
    if (getSocketConnectTimeout()) {
      doWork();
    }
  }
  
  public EchoClientTest(
      String host, int port, int timeout, boolean b) {
    this.host = host;
    this.port = port;
    this.timeout = timeout;
    if (getSocketSetTimeout()) {
      doWork();
    }
  }

  public boolean getSocketNoTimeout() {
    try {
      System.out.println("Not connected, waiting...");
      socket = new Socket(host, port);
      System.out.println(
        "Connected. Enter 'quit' to end.");
    } catch (UnknownHostException e) {
      System.err.println(
        "Don't know about host: " + host + ".");
      return false;
    } catch (IOException ioe) {
      System.err.println(
        "Connect: " + ioe.getMessage());
      return false;
    }
    return true;
  }

  public boolean getSocketConnectTimeout() {
    try {
      System.out.println("Not connected, waiting...");
      socketAddress = 
         new InetSocketAddress(host, port);
      socket = new Socket();
      socket.connect(socketAddress, timeout);
      System.out.println(
        "Connected. Enter 'quit' to end.");
    } catch(UnknownHostException e) {
      System.err.println("Don't know about host: " + 
        host + ".");
      return false;
    } catch(SocketTimeoutException ste) {
      System.err.println("Timeout: " +
        ste.getMessage());
      return false;
    } catch(IOException ioe) {
      System.err.println("Connect: " +
        ioe.getMessage());
      return false;
    }
    return true;
  }

  public boolean getSocketSetTimeout() {
    try {
      socketAddress = 
         new InetSocketAddress(host, port);
      socket = new Socket();
      try {
        socket.setSoTimeout(timeout);
      } catch (SocketException se) {
        System.err.println("Could not set timeout: " +
          se.getMessage());
        return false;
      }
      System.out.println("Not connected, waiting...");
      socket.connect(socketAddress);
      System.out.println(
        "Connected. Enter 'quit' to end.");
    } catch (UnknownHostException e) {
      System.err.println("Don't know about host: " +
        host + ".");
      return false;
    } catch (SocketTimeoutException ste) {
      System.err.println("Timeout: " +
        ste.getMessage());
      return false;
    } catch (IOException ioe) {
      System.err.println("Connect: " +
        ioe.getMessage());
      return false;
    }
    return true;
  }

  public void doWork() {
    try {
      writer = new PrintWriter(
        socket.getOutputStream(), true);
      reader = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
    } catch (IOException e) {
      System.err.println(
        "Couldn't get I/O for the connection to: " +
        host + ".");
      return;
    }

    try {
      BufferedReader stdIn = new BufferedReader(
        new InputStreamReader(System.in));
      String userInput;

      while ((userInput = stdIn.readLine()) != null) {
        writer.println(userInput);
        System.out.println("echo: " +
          reader.readLine());
        if (userInput.equals("quit")) {
          return;
        }
      }

      writer.close();
      reader.close();
      stdIn.close();
      socket.close();
    } catch(IOException ioe) {
      System.err.println("IOException: " +
        ioe.getMessage());
      return;
    }
  }

  public static void main(String args[]) {
    int thePort = 0;
    int theTimeout = 0;

    if (args.length == 1 && args[0].equals("?")) {
      System.out.println(
        "No args: localhost, port 7,  no timeout.");
      System.out.println(
        "1 arg: arghost, port 7, no timeout.");
      System.out.println(
        "2 args: argHost, argPort, no timeout.");
      System.out.println(
        "3 args: argHost, argPort, argTimeout, immediate connect.");
      System.out.println(
        "4 args: argHost, argPort, argTimeout, argAny - set, then connect.");

      return;
    }

    System.out.println("Start time: " + new Date());

    if (args.length == 0) {
      new EchoClientTest();
    } else {
      String theHost = args[0];
      //set up port, timeout, if sent
      if (args.length > 1) {
        try {
          thePort = Integer.parseInt(args[1]);
          if (args.length > 2) {
            theTimeout = Integer.parseInt(args[2]);
          }
        } catch(NumberFormatException nfe) {
          System.out.println(
             "Invalid port or timeout value.");
          return;
        }
      }

      if (args.length == 1) {
        new EchoClientTest(theHost);
      } else if (args.length == 2) {
        new EchoClientTest(theHost, thePort);
      } else if (args.length == 3) {
        new EchoClientTest(
          theHost, thePort, theTimeout);
      } else if (args.length == 4) {
        new EchoClientTest(
          theHost, thePort, theTimeout, true);
      }
    }
    System.out.println("End time: " + new Date());
  }
}

For cases where a connect timeout isn't specified, the default timeout is specific to the operating system or kernel setting on the machine to which you are connecting.

Overall, the combined new and old settings allow more flexible socket creation, binding, and connection. Use accordingly, and your programs should never sit waiting without a way out.

For more information on timeouts in socket connections, see "Networking: New Features and Enhancements".

Pixel

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Core JavaTM Technologies Tech Tips to: jdc-webmaster@sun.com

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EETM).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2METM).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Core Java Technologies Tech Tips
November 19, 2002


Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems, Inc.
Please send me newsletters in text.
Please unsubscribe me from this newsletter.