Return-Path: <env_1270573@hermes.java.sun.com>
Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id SAA10191; Tue, 25 Sep 2001 18:57:55 -0400 (EDT)
Received: from hermes.java.sun.com (hermes-db.java.sun.com [204.160.241.157])
	by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id SAA15474
	for <alexp@mit.edu>; Tue, 25 Sep 2001 18:53:51 -0400 (EDT)
Message-Id: <200109252253.SAA15474@pacific-carrier-annex.mit.edu>
Date: Tue, 25 Sep 2001 15:53:51 PDT
From: "JDC Tech Tips" <body_1270573@hermes.java.sun.com>
To: alexp@MIT.EDU
Subject: JDC Tech Tips  September 25, 2001
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
September 25, 2001. This issue covers:

         * Generating Integer Random Numbers 
         * Displaying Element-Level Tool Tips for Swing 
           Components

These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.       

This issue of the JDC Tech Tips is written by John Zukowski, 
president of JZ Ventures, Inc. (http://www.jzventures.com).

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0925.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
GENERATING INTEGER RANDOM NUMBERS

There are many ways to generate random numbers with the base 
libraries found in the Java 2 SDK, Standard Edition. If you 
haven't kept up to date with changes to the libraries, you might 
be using an inefficient mechanism or, possibly worse, getting 
results that are not uniformly distributed. Here's a look at a
good way to generate integer random numbers, and then a look at 
what's not so good about some other ways.

The java.util.Random class has been available for generating 
random numbers since the initial release of the system libraries.
Starting with the 1.2 release of the Java 2 SDK, Standard 
Edition, the Random class has a nextInt() method that accepts an 
integer argument:

    public int nextInt(int n)

Given some value n, nextInt(n) returns a value greater than or 
equal to zero but less then that value: 0 <= nextInt(n) < n.

All you have to do is create a Random object first, then call 
nextInt(n) to return the next random int value.

Here's a demonstration. The following code snippet generates 
a large set of random numbers and prints out the average: 

     int count = 1000000;
     int range = Integer.MAX_VALUE / 3 * 2;
     double sum = 0;
     Random rand = new Random();
     for (int i=0; i<count; i++) {
       sum += rand.nextInt(range);
     }
     System.out.println(sum/count);

After looping a million times, the average value should be 
approximately at the midpoint of the selected range.

So far, that isn't too complicated, but what's special about 
using nextInt(n)? Why is using nextInt(n) better than (1) using 
the older nextInt() method, with no range, then (2) using 
Math.abs() to get the absolute value, and then (3) using the 
mod (%) operator to get the value into the right range? The
latter approach is demonstrated in the following code snippet.
While there are a few extra operations in this approach, isn't
it just as good as the first approach?

    sum = 0;
    for (int i=0; i<count; i++) {
      sum += Math.abs(rand.nextInt()) % range;
    }
    System.out.println(sum/count);

There are actually three problems with the latter approach. 

First, nextInt() is equally likely to return a value between 
Integer.MIN_VALUE and Integer.MAX_VALUE. If you take the absolute 
value of Integer.MIN_VALUE, the result is not a positive number. 
In fact, the result of Math.abs(Integer.MIN_VALUE) is 
Integer.MIN_VALUE. So, on rare occasions, you'll get back a 
negative number. Given the rarity of the event, 1 out of 
2^31 times, the likelihood of the event happening during testing, 
and being repeatable, is highly unlikely.

Second, when you mod (%) the results of nextInt(), you 
effectively reduce the randomness of the results. The low order 
bits of random numbers can repeat more regularly than the entire 
number. This is a known issue with pseudo-random number 
generators, and so it's another reason not to use mod (%).

Finally, and possibly worst of all, the results are not evenly 
distributed. If you execute the loops in the two approaches, the 
first loop will produce results above 715 million, with a 
midpoint of the range at 715,827,882. That is within an 
acceptable tolerance for randomness. Surprisingly, the results of 
the second loop are consistently less than 600,000,000.

How can the second loop be so far off base? Essentially, the 
problem is that the mapping is unfair. When you use the mod (%) 
operator, you are taking values that are too large and squeezing 
them into the low end. This favors the lesser values. Compare 
this to rolling a single die with three other friends. Because 
there are only four of you and six possible values, the mapping 
for the first four values is easy. Die value 1 is mapped to 
person 1, value 2 to person 2, and so on. Now what about the 
larger values, 5 and 6? If you take the same approach as the 
mod (%) operator, you map the large values in such a way that
any time a 5 is rolled, person 1 wins, and any time a 6 is rolled, 
person 2 wins. Is this fair? Well, that's what happens when you 
mod (%) the results of nextInt().

Using the nextInt(range) method solves all three of these problems.

That leaves the random() method of the Math class as another 
possible way to generate random numbers. Using that method can't 
be that bad, can it? You just have to multiply the result by the 
range:

    sum = 0;
    for (int i=0; i<count; i++) {
      sum += (int)(Math.random() * range);
    }
    System.out.println(sum/count);

Well, using this method at least doesn't have any of the problems 
of nextInt(). You can't get a negative number back, you are not 
using the mod (%) operator so you don't run into the low-order 
byte range problem. And the range is uniform.

What is the problem, though, is that Math.random() uses floating 
point arithmetic, and both versions of nextInt() work with only 
integer operations. Math.random() can be up to four times slower 
because of its use of floating point operations. Throw in the 
cast, and the operation is even slower.

Because it avoids the problems inherent in using the other 
approaches, using the nextInt(range) method of Random is a better
way to generate integer random numbers.

Here's a complete program you can use to test the different
approaches discussed in this tip.

    import java.util.*;
    import java.text.*;

    public class RandomTest {
      public static void main(String args[]) {
        NumberFormat nf = NumberFormat.getInstance();
        int count = 1000000;
        int range = Integer.MAX_VALUE / 3 * 2;
        System.out.println("Midpoint: " + 
                                   nf.format(range/2));
        double sum = 0;
        Random rand = new Random();
        for (int i=0; i<count; i++) {
          sum += rand.nextInt(range);
        }
        System.out.println("Good    : " + 
                                 nf.format(sum/count));
        sum = 0;
        for (int i=0; i<count; i++) {
          sum += Math.abs(rand.nextInt()) % range;
        }
        System.out.println("Bad     : " + 
                                 nf.format(sum/count));
        sum = 0;
        for (int i=0; i<count; i++) {
          sum += (int)(Math.random() * range);
        }
        System.out.println("Longer  : " + 
                                 nf.format(sum/count));
      }
    }

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DISPLAYING ELEMENT-LEVEL TOOL TIPS FOR SWING COMPONENTS

Tool tips are those little popup windows that appear when you 
rest your mouse pointer over a control such as a button or list. 
All Swing components support tool tips. To establish a tool tip 
for a control, call setToolTipText() for the component. When a 
user rests the mouse pointer over the control, the tool tip 
automatically appears. Typically, you want one text string
for an entire component. But there can be times when you want
the text to be more flexible. For instance, in the case of a 
JList, JTree, or JTable component, you might want to display tool 
tip text based on which list entry, tree node, or table cell the 
mouse pointer is over. How can you customize the text?

There are two ways you can create custom tool tips for these 
Swing components. You can customize the control and override the 
public String getToolTipText(MouseEvent event) method, or you can 
customize the renderer for the control and let the renderer deal 
with the tool tips. 

If you decide to override the getToolTipText() method, you have 
to understand that the passed-in MouseEvent contains the mouse 
coordinates over the control. It is your responsibility to 
determine what element the cursor is on. Then, you have to look 
up the appropriate tool tip text for the element and return that 
text. You must then manually register the component with the 
ToolTipManager. If you called the setToolTipText() method for the 
control, a call to the registerComponent() method of 
ToolTipManager would have been done for you, but because you 
don't need to call setToolTipText(), you must do the registration 
yourself. 

Here's an example that demonstrates this first approach. The 
example uses the keys for the system properties (such as
java.runtime.name) as the list entries. The example uses the 
property values (such as Java 2, Runtime Environment, Standard 
Edition) as the tool tip text for those entries.

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

    public class ListTest extends JList {

      DefaultListModel model;
      Properties tipProps;

      public ListTest(Properties props) {
        model = new DefaultListModel();
        setModel(model);
        ToolTipManager.sharedInstance().registerComponent(
                                                 this);

        tipProps = props;
        Enumeration enum = props.propertyNames();
        while (enum.hasMoreElements()) {
          model.addElement(enum.nextElement());
        }
      }
      public String getToolTipText(MouseEvent event) {
        Point p = event.getPoint();
        int location = locationToIndex(p);
        String key = (String)model.getElementAt(
                                            location);
        String tip = tipProps.getProperty(key);
        return tip;
      }
      public static void main (String args[]) {
        JFrame frame = new JFrame("Custom Tips");
        frame.setDefaultCloseOperation(
                                 JFrame.EXIT_ON_CLOSE);
        Properties props = System.getProperties();
        ListTest list = new ListTest(props);
        JScrollPane scrollPane = new JScrollPane(list);
        frame.getContentPane().add(scrollPane);
        frame.setSize(300, 300);
        frame.show();
      }
    }

If you decide instead to customize the cell renderer, you have to 
do a little more work at first, but you don't have to determine 
what element is under the mouse at run time. If you find that the 
tool tips are hardly used, though, this extra work might not be 
necessary. That's because getting cell renderers happens 
frequently, and the tool tip setup happens every time the cell is 
rendered. It might be better to use the first mechanism. 
Essentially, what it involves is calling the setToolTipText() 
method for the cell renderer.

Here's an example that demonstrates the cell renderer approach.
The example is taken from the book "John Zukowski's Definitive 
Guide to Swing for Java 2, Second Edition" published by Apress
(http://www.apress.com/catalog/book/189311578X/).

First the renderer: this code essentially reuses a 
DefaultTreeCellRenderer as the renderer, setting its tip text.

    import javax.swing.*;
    import javax.swing.tree.*;
    import java.awt.*;
    import java.util.*;

    public class ToolTipTreeCellRenderer 
        implements TreeCellRenderer {
      DefaultTreeCellRenderer renderer = 
        new DefaultTreeCellRenderer();
      Dictionary tipTable;

      public ToolTipTreeCellRenderer (
                                 Dictionary tipTable) {
        this.tipTable = tipTable;
      }

      public Component getTreeCellRendererComponent(
          JTree tree, Object value, boolean selected, 
          boolean expanded, boolean leaf, int row, 
          boolean hasFocus) {
        renderer.getTreeCellRendererComponent(
          tree, value, selected, expanded, leaf, row, 
                                            hasFocus);
        if (value != null) {
          Object tipKey;
          if (
             value instanceof DefaultMutableTreeNode) {
             tipKey = 
              ((DefaultMutableTreeNode)value).getUserObject();
          } else {
            tipKey = tree.convertValueToText(value, 
              selected, expanded, leaf, row, hasFocus);
          }
          Object tip = tipTable.get(tipKey);
          if (tip != null) {
            renderer.setToolTipText(tip.toString());
          } else {
            renderer.setToolTipText(null);
          }
        }
        return renderer;
      }
    }

The test program then just registers the tree with the 
ToolTipManager and registers the renderer. There is no need to 
subclass JTree. Like the JList example, the system properties are 
used for the component elements. In this case, the tree is only 
one level deep.

    import javax.swing.*;
    import javax.swing.tree.*;
    import java.awt.*;
    import java.util.*;

    public class TreeTips {
      public static void main(String args[]) {
        JFrame frame = new JFrame("Custom Tree Tips");
        frame.setDefaultCloseOperation(
                                 JFrame.EXIT_ON_CLOSE);
        Properties props = System.getProperties();
        JTree tree = new JTree(props);
        ToolTipManager.sharedInstance().registerComponent(
                                                 tree);
        TreeCellRenderer renderer = 
                    new ToolTipTreeCellRenderer(props);
        tree.setCellRenderer(renderer);
        JScrollPane scrollPane = new JScrollPane(tree);
        frame.getContentPane().add(
                      scrollPane, BorderLayout.CENTER);
        frame.setSize(300, 150);
        frame.setVisible(true);
      }
    }

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

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

* FEEDBACK
  Comments? Send your feedback on the JDC Tech Tips to: 
  jdc-webmaster@sun.com

* SUBSCRIBE/UNSUBSCRIBE
  - To subscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), choose
    the newsletters you want to subscribe to and click "Update".
  - To unsubscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), uncheck the
    appropriate checkbox, and click "Update".
  - To use our one-click unsubscribe facility, see the link at 
    the end of this email:
    
- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2001 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


JDC Tech Tips 
September 25, 2001

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


To use our one-click unsubscribe facility, select the following URL:
http://hermes.java.sun.com/unsubscribe?1270573
