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