Received: from SOUTH-STATION-ANNEX.MIT.EDU by po10.MIT.EDU (5.61/4.7) id AA15249; Thu, 14 Sep 00 15:36:53 EDT
Received: from hermes.javasoft.com by MIT.EDU with SMTP
	id AA02081; Thu, 14 Sep 00 15:35:21 EDT
Received: (from nobody@localhost)
	by hermes.java.sun.com (8.9.3+Sun/8.9.1) id TAA26457;
	Thu, 14 Sep 2000 19:37:36 GMT
Date: Thu, 14 Sep 2000 19:37:36 GMT
Message-Id: <200009141937.TAA26457@hermes.java.sun.com>
X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_29/mqueue6
X-Mailing: 255
From: JDCTechTips@sun.com
Subject: JDC Tech Tips  September 12, 2000
To: JDCMember@sun.com
Reply-To: JDCTechTips@sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
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 12, 2000. This issue covers:

         * Using Class Methods and Variables
         * Using Progress Bars and Monitors in Java GUI 
           Applications
                  
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

You can view this issue of the Tech Tips on the Web at
http://developer.java.sun.com/developer/TechTips/2000/tt0912.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

USING CLASS METHODS AND VARIABLES

Suppose that you're designing a Java class to do some type of time
scheduling. One of the things you need within the class is a list 
of the number of days in each month of the year. Another thing you 
need is a method that determines if a given calendar year (like 
1900) is a leap year. The features might look like this:

    class Schedule {
        private int days_in_month[] =
            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

        private boolean isLeapYear(int year) {
            if (year % 4 != 0) {
                return false;
            }
            if (year % 400 == 0) {
                return true;
            }
            return (year % 100 != 0);
        }
    }

Implementing the class in this way will work, but there's a better
way to structure the list and the method.

Consider that a table of the number of days in each month is a 
fixed set of values that does not change. In other words, January 
always has 31 days. In the class above, each instance (object) of 
Schedule will contain the same table of 12 values.

This duplication is wasteful of space, and it gives the false
impression that the table is somehow different in each object, 
even though it's not. A better way to structure the table is like 
this:

    private static final int days_in_month[] =
        {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

The "final" keyword means that the table does not change after 
initialization, and the "static" keyword means that there is 
a single copy of the table shared across all instances of the 
class. A variable declared this way is called a "class variable" 
or "static field," as contrasted with "instance variable." 

In a similar way, consider that the isLeapYear method does not
actually do anything with object-specific data. It simply accepts
an integer parameter representing a year, and returns a true/false
value. So it would be better to say:

    private static boolean isLeapYear(int year) {
        if (year % 4 != 0) {
            return false;
        }
        if (year % 400 == 0) {
            return true;
        }
        return (year % 100 != 0);
    }

This is an example of a "class method" or "static method".

There are several interesting points to note about class methods 
and variables, some of them obvious, some not. The first point is
that a class variable exists even if no instances of the class 
have been created. For example, if you have:

    class A {
        static int x;
    }

You can say:

    int i = A.x;

in your program, whether or not you have created any A objects.

Another point is that class methods do not operate on specific
objects, so there's no "this" reference within such methods:

    class A {
        static int x = 37;
        int y = 47;
        static void f() {
            A aref = this;  // invalid
            int i = x;      // valid
            int j = y;      // invalid
            g();            // invalid
        }
        void g() {
            A aref = this;  // valid
            int i = x;      // valid
            int j = y;      // valid
            f();            // valid
        }
    }

The invalid cases in the previous example are ones that require 
an object. For example, "y" is an instance variable within objects 
of type A. Because the static method f does not operate on a 
particular object of A, there's no y field to access. 

When you access a static field outside of its class, you need to 
qualify it with the class name:

    class A {
        public static int x = 37;
    }

    class B {
        static int i = A.x;
    }

A less desirable but legal form of qualification looks like this:

    class A {
        public static int x = 37;
    }

    class B {
        A aref = new A();
        int i = aref.x;
    }

This usage gives the false impression that "x" is an instance
variable of an object of A. It's possible to take such usage even
further, and say:

    class A {
        public static int x = 37;
    }

    class B {
        A aref = null;
        int i = aref.x;
    }

This usage is legal and will not trigger an exception; since x is
not an instance variable, there is no actual need to access the
object referenced by aref.

Given these details of how class methods and variables work, where
would you want to use them? One type of usage was illustrated 
above with the Schedule class -- you have some common data shared 
by all objects of a particular type. Or perhaps you have some 
methods that operate only on class variables. Maybe the methods 
don't operate on class data at all, but are somehow related to the 
function of the class; the isLeapYear method illustrates this form 
of usage.

You can also use class methods and variables as a packaging 
technique. Suppose you have some legacy code that you would like 
to convert. Imagine that the code uses some global variables. 
Typically you don't want to use global variables (it's impossible 
to do so with the Java language). But you'd like to come up with 
an equivalent, to help the conversion process along. Here's one 
way you can structure the code:

    public class Globals {
        private Globals() {}
        public static int A = 1;
        public static int B = 2;
        public static int C = 3;
    }

Using this class, you have three "pseudo globals" with names
Globals.A, Globals.B, and Globals.C, which you can use throughout
your application. The private constructor for Globals emphasizes
that the class is being used simply as a packaging vehicle. It's
not legal to actually create instances of the class.

This particular structuring technique is not always desirable, 
because it's easy to change field values from all over your code. 
An alternative approach is to make the static fields private, and 
allow changes to them only through accessor methods. Using this 
approach, you can more readily trap field changes. Here's an 
example:

    public class Globals {
        private Globals() {}
        private static int A = 1;
        public static void setA(int i) {A = i;}
        public static int getA() {return A;}
    }

You can also use a class to package methods. For example, two of
the class methods in java.lang.System are:

    arraycopy

    currentTimeMillis

These really don't have anything to do with each other, except 
that they're both low-level system methods that provide services 
to the user. A set of such methods are grouped together in the 
System class.

A final use of class variables is to group together a set of 
constants:

    public class Constants {
        private Constants() {}
        public static final int A = 1;
        public static final int B = 2;
        public static final int C = 3;
    }

You can do a similar thing with an interface:

    public interface Constants {
        int A = 1;
        int B = 2;
        int C = 3;
    }

What are the differences between using classes and interfaces to
group constants? Here are several:

1. Interface fields are implicitly public, static, and final.

2. You cannot change an interface field once it's initialized. 
   By contrast, you can change a field in a class if the field is 
   non-final; if you're really establishing a set of constants, 
   you probably don't want to do this.

3. You can use static initialization blocks to set up the fields
   in a class. For example:

    class Constants {
        public static final int A;
        static {
            A = 37;
        }
    }

4. You can implement an interface in a class to gain convenient
   access to the interface's constants.

For further information about class methods and class variables,
see sections 2.2.2, Static Fields, and 2.6.1, Static Methods in 
"The Java Programming Language Third Edition" by Arnold, Gosling,
and Holmes 
(http://java.sun.com/docs/books/javaprog/thirdedition/).


USING PROGRESS BARS AND MONITORS IN JAVA GUI APPLICATIONS

If you have a GUI application that performs a time-consuming task,
it's desirable to let the user know that the task is being 
processed. It's also a good idea to give the user a progress 
indicator, such as "the task is X% finished."

The Java Swing library has a couple of mechanisms for displaying
progress. This tip examines them in the context of a real-life 
application. The application is one that searches for a string in 
all files under a starting directory. For example, if you're on 
a UNIX system and you specify "/usr" as the starting directory, 
and a pattern "programming", the application displays a list of 
all the files that contain "programming" somewhere within them.

This application is time-consuming. It can take a few seconds
to a few minutes to run, depending on how big the directory
structure is and how fast your computer runs.

The search process has two distinct phases. The first iterates
across the directory structure and makes a list of all the files.
The second phase actually searches the files.

It's not possible in the strictest sense to indicate progress 
during the first phase. Progress is based on percentage complete.
Here there's no way to obtain the percentage completed, because 
it's not possible to tell in advance how many files are in the 
directory. In the second phase, however, it's possible to
get at least a rough idea of progress. The program can determine 
that, for example, 59 out of 147 files have been searched so far.

The application code looks like this:

    import java.awt.GridLayout;
    import java.awt.Cursor;
    import java.awt.event.*;
    import java.util.*;
    import java.io.*;
    import javax.swing.*;
    import java.lang.reflect.InvocationTargetException;

    public class ProgressDemo {
    
        String startdir;        // starting directory for search
        String patt;            // pattern to search for
        JTextArea outarea;      // output area for file pathnames
        JFrame frame;           // frame
        JProgressBar progbar;   // progress bar
        JLabel fileslab;        // number of files found
        boolean search_flag;    // true if search in progress
    
        // nested class used to do actual searching
    
        class Search extends Thread {
    
            // do GUI updates

            void doUpdate(Runnable r) {
                try {
                    SwingUtilities.invokeAndWait(r);
                }
                catch (InvocationTargetException e1) {
                    System.err.println(e1);
                }
                catch (InterruptedException e2) {
                    System.err.println(e2);
                }
            }

            // get a list of all the files under a given directory
    
            void getFileList(File f, List list) {
    
                // recurse if a directory
    
                if (f.isDirectory()) {
                    String entries[] = f.list();
                    for (int i = 0; i < entries.length; i++) {
                        getFileList(new File(f, entries[i]),
                            list);
                    }
                }
    
                // for plain files, add to list and
                // update progress bar
    
                else if (f.isFile()) {
                    list.add(f.getPath());
                    final int size = list.size();
                    if (size % 100 != 0) {
                        return;
                    }
                    doUpdate(new Runnable() {
                        public void run() {
                            progbar.setValue(size % 1000);
                        }
                    });
                }
            }
    
            // check whether a file contains the specified pattern
    
            boolean fileMatch(String fn, String patt) {
                boolean found = false;
    
                try {
                    FileReader fr = new FileReader(fn);
                    BufferedReader br = new BufferedReader(fr);
                    String str;
                    while ((str = br.readLine()) != null) {
                        if (str.indexOf(patt) != -1) {
                            found = true;
                            break;
                        }
                    }
                    br.close();
                }
                catch (IOException e) {
                    System.err.println(e);
                }
    
                return found;
            }
    
            // perform the search
    
            public void run() {
                List filelist = new ArrayList();
                final String sep =
                    System.getProperty("line.separator");
    
                // clear old output
    
                doUpdate(new Runnable() {
                    public void run() {
                        outarea.setText("");
                        fileslab.setText("");
                    }
                });
    
                // get the list of files and display a count
    
                getFileList(new File(startdir), filelist);
                final int size = filelist.size();
                doUpdate(new Runnable() {
                    public void run() {
                        progbar.setValue(0);
                        fileslab.setText("Found " + size +
                            " files, now searching ...");
                    }
                });
    
                // set up a progress monitor
    
                final ProgressMonitor pm = new ProgressMonitor(
                    frame, "Searching files", "", 0, size - 1);
                pm.setMillisToDecideToPopup(0);
                pm.setMillisToPopup(0);
    
                // iterate across the files, updating
                // the progress monitor
    
                for (int i = 0; i < size; i++) {
                    final String fn = (String)filelist.get(i);
                    final int curr = i;
                    if (pm.isCanceled()) {
                        break;
                    }
                    final boolean b = fileMatch(fn, patt);
                    doUpdate(new Runnable() {
                        public void run() {
                            pm.setProgress(curr);
                            pm.setNote(fn);
                            if (b) {
                                outarea.append(fn + sep);
                            }
                        }
                    });
                }
    
                // close the progress monitor and
                // set the caret position in the output
                // area to the beginning of the file list
    
                doUpdate(new Runnable() {
                    public void run() {
                        pm.close();
                        outarea.setCaretPosition(0);
                        fileslab.setText("");
                    }
                });
    
                search_flag = false;
            }
        }
    
        public ProgressDemo() {
            frame = new JFrame("ProgressDemo");
    
            // set up the window closer for the frame
    
            frame.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
    
            // set up panels
    
            JPanel paneltop = new JPanel();
            JPanel panelbot = new JPanel();
            paneltop.setLayout(new GridLayout(5, 1));
    
            JPanel panel1 = new JPanel();
            panel1.add(new JLabel("Starting Directory"));
            final JTextField dirfield = new JTextField(20);
            panel1.add(dirfield);
    
            JPanel panel2 = new JPanel();
            panel2.add(new JLabel("Search Pattern"));
            final JTextField pattfield = new JTextField(20);
            panel2.add(pattfield);
    
            JPanel panel3 = new JPanel();
            JButton button = new JButton("Search");
            panel3.add(button);
    
            JPanel panel4 = new JPanel();
            progbar = new JProgressBar(0, 999);
            panel4.add(progbar);
    
            JPanel panel5 = new JPanel();
            fileslab = new JLabel();
            panel5.add(fileslab);
    
            JPanel panel6 = new JPanel();
            outarea = new JTextArea(8, 40);
            outarea.setEditable(false);
            JScrollPane jsp = new JScrollPane(outarea,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            panel6.add(jsp);
    
            // processing for "Search" button
    
            button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    startdir = dirfield.getText();
                    patt = pattfield.getText();
                    if (startdir == null ||
                        startdir.trim().equals("") ||
                        patt == null ||
                        patt.trim().equals("")) {
                        JOptionPane.showMessageDialog(
                            frame, "Invalid input", "Error",
                            JOptionPane.ERROR_MESSAGE);
                    }
                    else if (search_flag) {
                        JOptionPane.showMessageDialog(
                            frame, "Search in progress",
                            "Error", JOptionPane.ERROR_MESSAGE);
                    }
                    else {
                        search_flag = true;
                        new Search().start();
                    }
                }
            });
    
            paneltop.add(panel1);
            paneltop.add(panel2);
            paneltop.add(panel3);
            paneltop.add(panel4);
            paneltop.add(panel5);
    
            panelbot.add(panel6);
    
            JPanel panel = new JPanel();
            panel.setLayout(new GridLayout(2, 1));
            panel.add(paneltop);
            panel.add(panelbot);
    
            // display the frame
    
            frame.getContentPane().add(panel);
            frame.pack();
            frame.setLocation(200, 200);
            frame.setVisible(true);
        }
    
        public static void main(String args[]) {
            new ProgressDemo();
        }
    }

The main method creates an object of type ProgressDemo. This part
of the application sets up the various panels, input areas, and
buttons.

The action listener for the Search button validates input. It then
creates and starts a thread of type Search. Search is an inner
class used to do the actual searching. Searching is done via a
separate thread because searching is time-consuming, and it's a 
bad idea to perform lengthy processing from the event dispatch 
thread. The event dispatch thread is used to handle the Search 
button selection and the call of the button's action listener. If 
the actual search is also performed in this thread, the thread 
cannot immediately respond to other events. An example of another 
event might be clicking on the top right of the main window to 
terminate the application.

The actual searching is done after control is transferred to the 
run method of the Search class. One piece of code you'll see 
repeatedly in this part of the code is:

    doUpdate(new Runnable() {
        public void run() {
            ...
        }
    });

Although searching is not done from the event dispatch thread, 
it is desirable that the event dispatch thread be used to update 
the GUI. The repeated code above is used because Swing is not 
thread-safe. The code adds a runnable object to the event 
dispatch thread queue. The run method for the object is called 
when the object gets to the front of the queue.

The doUpdate method is implemented using 
SwingUtilities.invokeAndWait. This means that doUpdate does not 
return until the run method returns. It's also possible to use 
SwingUtilities.invokeLater here, but using invokeAndWait makes 
for smoother GUI updating of the progress bar.

The list of files to search is accumulated by doing a recursive
directory walk, using java.io.File. Because the program doesn't 
know how many files there are, it can't indicate a percentage 
complete; instead it repeatedly fills a JProgressBar object.

An object of type JProgressBar is initially created, with limits 
in the range 0 to 999. The bar is updated as files are found 
during the directory walk. How "full" the bar is depends on the 
result of applying the modulo (%) operator on the count of number 
of files. In other words, the progress bar is empty with a value 
of 0, and full with a value of 999. If the program finds 500 or 
1500 or 2500 files thus far, the bar is half full. This scheme 
doesn't indicate a percentage complete, but simply that the 
directory enumeration is "doing something".

After tabulating the list of files, a ProgressMonitor object is
created. You specify a message to be displayed during the 
operation (here it's "Searching files", a note describing the 
state of the operation (in this example, it's null), and the 
minimum and maximum values for this object (here it's
0, and the number of files - 1, respectively). Then 
setProgress(currentvalue) is called to indicate progress has been
made. The logic in the ProgressMonitor class determines whether 
to pop up a display showing how far along the processing is. 
Because the program knows the number of files to be searched,
this approach works pretty well.

As each file is searched, ProgressDemo calls setProgress and 
setNote. These ProgressMonitor methods periodically update
the display as progress is being made. Note that the progress 
monitor might not display if the searching to be done is very 
short. ProgressMonitor has methods you can call to tailor
the amount of time before the monitor pops up".

Another approach for the progress monitor would be to keep track 
of file lengths instead of file counts. This approach is slightly 
more complicated, but gives a better indication of progress.
This is especially true if you're searching files of widely 
varying lengths. For example, say you have 20 files. The first 
10 are one byte long, and the last 10 are each one million bytes 
long. In this case, the progress monitor display will be 
misleading if it's based on file counts.

There's also a class ProgressMonitorInputStream designed
specifically for indicating progress while reading files.

A good place to read more about progress bars and progress 
monitors is "Graphic Java - Mastering the JFC 3rd Edition 
Volume II Swing" by David Geary. See especially "Swing and 
Threads" in Chapter 2, and "Progress Bars, Sliders, and 
Separators" in Chapter 11.

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

- NOTE
The names on the JDC mailing lists are used for internal Sun
Microsystems(tm) purposes only. To remove your name from a JDC
mailing list, see Subscribe/Unsubscribe below.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- SUBSCRIBE/UNSUBSCRIBE
The JDC Tech Tips are sent to you because you elected to subscribe.
To unsubscribe from this and any other JDC Email, select 
"Subscribe to free JDC newsletters" on the JDC front page. This 
displays the Subscriptions page, where you can change the current 
selections.

You need to be a JDC member to subscribe to the Tech Tips. To
become a JDC member, go to:

http://java.sun.com/jdc/

To subscribe to the Tech Tips and other JDC Email, select 
"Subscribe to free JDC newsletters" on the JDC front page. 


- ARCHIVES
You'll find the JDC Tech Tips archives at:

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


- COPYRIGHT
Copyright 2000 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://developer.java.sun.com/developer/copyright.html


This issue of the JDC Tech Tips is written by Glen McCluskey.

JDC Tech Tips 
September 12, 2000












