Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Packaging an applet into a JAR file An important use of the JAR utility is to optimize applet loading. JAR files solve the problem by compressing all of your .class files into a single file that is downloaded by the browser. Now you can create the right design without worrying about how many .class files it will generate, and the user will get a much faster download time. Consider TicTacToe.java. It looks like a single class, but in fact it contains five inner classes, so that’s six in all. Once you’ve compiled the program, you package it into a JAR file with the line: jar cf TicTacToe.jar *.class This assumes that the only .class files in the current directory are the ones from TicTacToe.java (otherwise, you’ll get extra baggage). Now you can create an HTML page with the new archive tag to indicate the name of the JAR file. Here is the basic applet tag: <head><title>TicTacToe Example Applet </title></head> <body> <applet code=TicTacToe.class archive=TicTacToe.jar width=200 height=100> </applet> </body> You’ll need to run this file through the HTMLconverter application that comes with the JDK in order to get it to work. Signing applets Because of the sandbox security model, unsigned applets are prevented from performing certain tasks on the client, like writing to a file or connecting to a local network. A signed applet verifies to the user that the person who claims to have created the applet actually did, and that the contents of the JAR file have not been tampered with since that file left the server. Consider an applet that wants to have access to the client’s file system and read and write some files. //: c14:signedapplet:FileAccessApplet.java // Demonstration of File dialog boxes. package c14.signedapplet; import import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.io.*; com.bruceeckel.swing.*; public class FileAccessApplet extends JApplet { private JTextField filename = new JTextField(), dir = new JTextField(); private JButton open = new JButton("Open"), save = new JButton("Save"); 1 private JEditorPane ep = new JEditorPane(); private JScrollPane jsp = new JScrollPane(); private File file; public void init() { JPanel p = new JPanel(); open.addActionListener(new OpenL()); p.add(open); save.addActionListener(new SaveL()); p.add(save); Container cp = getContentPane(); jsp.getViewport().add(ep); cp.add(jsp, BorderLayout.CENTER); cp.add(p, BorderLayout.SOUTH); dir.setEditable(false); save.setEnabled(false); ep.setContentType("text/html"); filename.setEditable(false); p = new JPanel(); p.setLayout(new GridLayout(2, 1)); p.add(filename); p.add(dir); cp.add(p, BorderLayout.NORTH); } class OpenL implements ActionListener { public void actionPerformed(ActionEvent e) { JFileChooser c = new JFileChooser(); c.setFileFilter(new TextFileFilter()); // Demonstrate "Open" dialog: int rVal = c.showOpenDialog(FileAccessApplet.this); if (rVal == JFileChooser.APPROVE_OPTION) { file = c.getSelectedFile(); filename.setText(file.getName()); dir.setText(c.getCurrentDirectory().toString()); try { System.out.println("Url is " + file.toURL()); ep.setPage(file.toURL()); // ep.repaint(); } catch (IOException ioe) { throw new RuntimeException(ioe); } } if (rVal == JFileChooser.CANCEL_OPTION) { filename.setText("You pressed cancel"); dir.setText(""); } else { save.setEnabled(true); 2 } } } class SaveL implements ActionListener { public void actionPerformed(ActionEvent e) { JFileChooser c = new JFileChooser(file); c.setSelectedFile(file); // Demonstrate "Save" dialog: int rVal = c.showSaveDialog(FileAccessApplet.this); if (rVal == JFileChooser.APPROVE_OPTION) { filename.setText(c.getSelectedFile().getName()); dir.setText(c.getCurrentDirectory().toString()); try { FileWriter fw = new FileWriter(file); ep.write(fw); } catch (IOException ioe) { throw new RuntimeException(ioe); } } if (rVal == JFileChooser.CANCEL_OPTION) { filename.setText("You pressed cancel"); dir.setText(""); } } } public class TextFileFilter extends javax.swing.filechooser.FileFilter { public boolean accept(File f) { return f.getName().endsWith(".txt") || f.isDirectory(); } public String getDescription() { return "Text Files (*.txt)"; } } public static void main(String[] args) { Console.run(new FileAccessApplet(), 500, 500); } } // /:~ 3 It appears to be an ordinary applet. However, as it stands, it would not be allowed to open and close files on a client’s system. To make this run as a signed applet, you need to put it into a JAR file and sign the JAR file. Once you have a JAR file, you will need a certificate or a key to sign it with. If you were a large corporation, you would apply to a signing authority like Verisign or Thawte, and they would issue you a certificate. This is used to sign code and thus identify to a user that you are indeed the provider of the code they are downloading, and that the code that has been deployed hasn’t been modified since you signed it. Essentially, the digital signature is a load of bits, and the signing authority vouches for you when someone downloads that signature. A certificate from a signing authority costs money and requires regular renewal. In our case we can just make a little self-signed one. This needs to be stored in a file somewhere (it is usually called the keychain). If you type: keytool –list then it will try to access the default file. If there is no file, then you need to create one, or specify an existing one. You might try to search for a file called “cacerts,” and then try keytool -list -file <path/filename> The default location is usually {java.home}/lib/security/cacerts where the java.home property points to the JRE home. You can also easily make a self-signed certificate for testing purposes using the keytool. If you have your Java “bin” directory in your executable path, 4 you can type: keytool –genkey –alias <keyname> -keystore <url> where keyname is the alias name that you want to give the key, say “mykeyname,” and url is the location of the file that stores your keys, usually the cacerts file as described above. You will now be prompted for the password. Unless you have changed the default, this will be “changeit” (a hint to do just that). Next you will be asked for your name, the organizational unit, the organization, city, state, and country. This information is stored in the certificate. Lastly, you will be asked for a password for that key. If you are really security conscious, you can give it a separate password, but the default password is the same as the keystore itself, and is usually adequate. The above information can be specified on the command line from within a build tool such as Ant. If you invoke the keytool utility with no parameters at the command prompt, it will give you a list of its numerous options. You might like to use the –valid option, for example, which enables you to specify how many days the key will be valid for. To confirm that your key is now in the cacerts file, type: keytool –list –keystore <url> and enter the password as before. Your key may be hidden among the other keys already in your certificate files. Your new certificate is self-signed and thus not actually trusted by a signing authority. If you use this certificate to sign a JAR file, the end user will get a warning, and a strong recommendation not to use your software. You and your users will have to tolerate this until you are prepared to pay for a trusted certificate for commercial purposes. To sign your JAR file, use the standard Java jarsigner tool as follows: jarsigner –keystore <url> <jarfile> <keyname> where url is the location of your cacerts file, jarfile is the name of your JAR file, and keyname is the alias that you gave to your key. You will again be prompted for the password. You now have a JAR file that can be identified as being signed with your key, and that can guarantee it has not been tampered with (i.e., no files have been changed, added, or removed) since you signed it. All you have to do now is make sure that the applet tag in your HTML file has an “archive” element, which specifies the name of your JAR file. The applet tag is somewhat more complicated for the plugin, but if you create a simple tag like: <APPLET CODE=package.AppletSubclass.class ARCHIVE = myjar.jar WIDTH=300 HEIGHT=200> </APPLET> and run the HTMLConverter tool on it (this is packaged with the freely downloadable JDK), it will create the correct applet tag for you. Now, when your applet is downloaded by a client, they will be informed that a signed applet is being loaded, and given the option of trusting the signer. As previously mentioned, your test certificate doesn’t have a very high degree of trust, and the user will get a warning to this effect. If they opt to trust your 5 applet, it will have full access to their system and behave as if it were an ordinary application. JNLP and Java Web Start Signed applets are powerful and can effectively take the place of an application, but they must run inside a Web browser. This requires the extra overhead of the browser running on the client machine, and also means that the user interface of the applet is limited and often visually confusing. The Web browser has its own set of menus and toolbars, which will appear above the applet. The Java Network Launch Protocol (JNLP) solves the problem without sacrificing the advantages of applets. With a JNLP application, you can download and install a standalone Java application onto the client’s machine. This can be run from the command prompt, a desktop icon, or the application manager that is installed with your JNLP implementation. The application can even be run from the Web site from which it was originally downloaded. A JNLP application can dynamically download resources from the Internet at run time, and the version can be automatically checked (if the user is connected to the Internet) . This means that it has all of the advantages of an applet together with the advantages of standalone applications. Because JNLP describes a protocol, not an implementation, you will need an implementation in order to use it. Java Web Start, or JAWS, is Sun’s freely available official reference implementation. All you need to do is download and install it, and if you are using it for development, make sure that the JAR files are in your classpath. If you are deploying your JNLP application from a Web server, you have to ensure that your server recognizes the MIME type application/x-java-jnlp-file. Creating a JNLP application is not difficult. You create a standard application that is archived in a JAR file, and then you provide a launch file, which is a simple XML file that gives the client all the information it needs to download and install your application. If you choose not to sign your JAR file, then you must make use of the services supplied by the JNLP API for each type of resource you want access to on the users machine. Here is a variation of the example using the JFileChooser dialog, but this time using the JNLP services to open it, so that the class can be deployed as a JNLP application in an unsigned JAR file. //: c14:jnlp:JnlpFileChooser.java // Opening files on a local machine with JNLP. // {Depends: javaws.jar} // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. package c14.jnlp; import import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.io.*; javax.jnlp.*; 6 public class JnlpFileChooser extends JFrame { private JTextField filename = new JTextField(); private JButton open = new JButton("Open"), save = new JButton("Save"); private JEditorPane ep = new JEditorPane(); private JScrollPane jsp = new JScrollPane(); private FileContents fileContents; public JnlpFileChooser() { JPanel p = new JPanel(); open.addActionListener(new OpenL()); p.add(open); save.addActionListener(new SaveL()); p.add(save); Container cp = getContentPane(); jsp.getViewport().add(ep); cp.add(jsp, BorderLayout.CENTER); cp.add(p, BorderLayout.SOUTH); filename.setEditable(false); p = new JPanel(); p.setLayout(new GridLayout(2, 1)); p.add(filename); cp.add(p, BorderLayout.NORTH); ep.setContentType("text"); save.setEnabled(false); } class OpenL implements ActionListener { public void actionPerformed(ActionEvent e) { FileOpenService fs = null; try { fs = (FileOpenService) ServiceManager .lookup("javax.jnlp.FileOpenService"); } catch (UnavailableServiceException use) { throw new RuntimeException(use); } if (fs != null) { try { fileContents = fs.openFileDialog(".", new String[] { "txt", "*" }); if (fileContents == null) return; filename.setText(fileContents.getName()); ep.read(fileContents.getInputStream(), null); } catch (Exception exc) { throw new RuntimeException(exc); } 7 save.setEnabled(true); } } } class SaveL implements ActionListener { public void actionPerformed(ActionEvent e) { FileSaveService fs = null; try { fs = (FileSaveService) ServiceManager .lookup("javax.jnlp.FileSaveService"); } catch (UnavailableServiceException use) { throw new RuntimeException(use); } if (fs != null) { try { fileContents = fs.saveFileDialog(".", new String[] { "txt" }, new ByteArrayInputStream(ep .getText().getBytes()), fileContents .getName()); if (fileContents == null) return; filename.setText(fileContents.getName()); } catch (Exception exc) { throw new RuntimeException(exc); } } } } public static void main(String[] args) { JnlpFileChooser fc = new JnlpFileChooser(); fc.setSize(400, 300); fc.setVisible(true); } } // /:~ Note that the FileOpenService and the FileCloseService classes are imported from the javax.jnlp package and that nowhere in the code is the JFileChooser dialog box referred to directly. The two services used here must be requested using the ServiceManager.lookup( ) method, and the resources on the client system can only be accessed via the objects returned from this method. In this case, the files on the client’s file system are being written to and read from using the FileContent interface, provided by the JNLP. Any attempt to access the resources directly by using, say, a File or a FileReader object would cause a SecurityException to be thrown in the same way that it would if you tried to use them from an unsigned applet. If you want to use these classes and not be restricted to the JNLP service interfaces, you must sign the JAR file. 8 Now that we have a runnable class that makes use of the JNLP services, all that is needed is for the class to be put into a JAR file and a launch file to be written. Here is an appropriate launch file for the preceding example. <?xml version="1.0" encoding="UTF-8"?> <jnlp codebase="__codebase__" href="jnlp/filechooser.jnlp"> <information> <title>FileChooser demo application</title> <vendor>Mindview Inc.</vendor> <description>Jnlp File choose Application</description> <description kind="short">A demonstration of opening, reading and writing a text file</description> <icon href="images/tijicon.gif"/> <offline-allowed/> </information> <resources> <j2se version="1.3+"/> <jar href="jnlp/jnlpfilechooser.jar" download="eager"/> </resources> <application-desc main-class="c14.jnlp.JnlpFileChooser"/> </jnlp> This launch file needs to be saved as a .jnlp file, in this case, filechooser.jnlp, in the same directory as the JAR file. As you can see, it is an XML file with one <jnlp> tag. This has a few subelements, which are mostly self-explanatory. The spec attribute of the jnlp element tells the client system what version of the JNLP the application can be run with. The codebase attribute points to the directory where this launch file and the resources can be found. Typically, it would be an HTTP URL pointing to a Web server, but in this case it points to a directory on the local machine, which is a good means of testing the application. The href attribute must specify the name of this file. The information tag has various subelements that provide information about the application. These are used by the Java Web Start administrative console or equivalent, which installs the JNLP application and allows the user to run it from the command line, make short cuts and so on. The resources tag serves a similar purpose as the applet tag in an HTML file. The j2se subelement specifies the version of the j2se that is needed to run the application, and the jar subelement specifies the JAR file in which the class is archived. The jar element has an attribute download, which can have the values “eager” or “lazy” that tell the JNLP implementation whether or not the entire archive needs to be downloaded before the application can be run. The application-desc attribute tells the JNLP implementation which class is the executable class, or entry point, to the JAR file. Another useful subelement of the jnlp tag is the security tag, not shown here. Here’s what a security tag looks like: <security> <all-permissions/> <security/> 9 You use the security tag when your application is deployed in a signed JAR file. It is not needed in the preceding example because the local resources are all accessed via the JNLP services. There are a few other tags available, the details of which can be found in the specification http://java.sun.com/products/javawebstart/downloadspec. html. Now that the .jnlp is written, you will need to add a hypertext link to it in an HTML page. This will be its download page. You might have a complex layout with a detailed introduction to your application, but as long as you have something like: <a href="filechooser.jnlp">click here</a> in your HTML file, then you will be able to initiate the installation of the JNLP application by clicking on the link. Once you have downloaded the application once, you will be able to configure it by using the administrative console. If you are using Java Web Start on Windows, then you will be prompted to make a short cut to your application the second time you use it. This behavior is configurable. Only two of the JNLP services are covered here, but there are seven services in the current release. Each is designed for a specific task such as printing, or cutting, and pasting to the clipboard. An in-depth discussion of these services is beyond the scope of this course. Programming techniques Binding events dynamically One of the benefits of the Swing event model is flexibility. You can add and remove event behavior with single method calls. The following example demonstrates this: //: c14:DynamicEvents.java // You can change event behavior dynamically. // Also shows multiple actions for an event. // <applet code=DynamicEvents // width=250 height=400></applet> // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import com.bruceeckel.swing.*; public class DynamicEvents extends JApplet { private java.util.List list = new ArrayList(); private int i = 0; private JButton b1 = new JButton("Button1"), b2 = new JButton("Button2"); private JTextArea txt = new JTextArea(); class B implements ActionListener { 10 public void actionPerformed(ActionEvent e) { txt.append("A button was pressed\n"); } } class CountListener implements ActionListener { private int index; public CountListener(int i) { index = i; } public void actionPerformed(ActionEvent e) { txt.append("Counted Listener " + index + "\n"); } } class B1 implements ActionListener { public void actionPerformed(ActionEvent e) { txt.append("Button 1 pressed\n"); ActionListener a = new CountListener(i++); list.add(a); b2.addActionListener(a); } } class B2 implements ActionListener { public void actionPerformed(ActionEvent e) { txt.append("Button2 pressed\n"); int end = list.size() - 1; if (end >= 0) { b2.removeActionListener((ActionListener) list.get(end)); list.remove(end); } } } public void init() { Container cp = getContentPane(); b1.addActionListener(new B()); b1.addActionListener(new B1()); b2.addActionListener(new B()); b2.addActionListener(new B2()); JPanel p = new JPanel(); p.add(b1); p.add(b2); cp.add(BorderLayout.NORTH, p); cp.add(new JScrollPane(txt)); } 11 public static void main(String[] args) { Console.run(new DynamicEvents(), 250, 400); } } // /:~ The new twists in this example are: 1. There is more than one listener attached to each Button. Usually, components handle events as multicast, meaning that you can register many listeners for a single event. In the special components in which an event is handled as unicast, you’ll get a TooManyListenersException. 2. During the execution of the program, listeners are dynamically added and removed from the Button b2. Adding is accomplished in the way you’ve seen before, but each component also has a removeXXXListener( ) method to remove each type of listener. This kind of flexibility provides much greater power in your programming. You should notice that event listeners are not guaranteed to be called in the order they are added (although most implementations do in fact work that way). Separating business logic from UI logic In general, you’ll want to design your classes so that each one does “only one thing.” This is particularly important when user-interface code is concerned, since it’s easy to tie up “what you’re doing” with “how you’re displaying it.” This way, not only can you reuse the business logic more easily, but it’s also easier to reuse the GUI. Another issue is multitiered systems, where the “business objects” reside on a completely separate machine. This central location of the business rules 12 allows changes to be instantly effective for all new transactions, and is thus a compelling way to set up a system. However, these business objects can be used in many different applications and so should not be tied to any particular mode of display. They should just perform the business operations and nothing more. The following example shows how easy it is to separate the business logic from the GUI code: package c14; //: c14:Separation.java // Separating GUI logic and business objects. // <applet code=Separation width=250 height=150></applet> // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. import javax.swing.*; import java.awt.*; import javax.swing.event.*; import java.awt.event.*; import java.applet.*; import com.bruceeckel.swing.*; class BusinessLogic { private int modifier; public BusinessLogic(int mod) { modifier = mod; } public void setModifier(int mod) { modifier = mod; } public int getModifier() { return modifier; } // Some business operations: public int calculation1(int arg) { return arg * modifier; } public int calculation2(int arg) { return arg + modifier; } } public class Separation extends JApplet { private JTextField t = new JTextField(15), mod = new JTextField(15); private JButton calc1 = new JButton("Calculation 1"), calc2 = new JButton( 13 "Calculation 2"); private BusinessLogic bl = new BusinessLogic(2); public static int getValue(JTextField tf) { try { return Integer.parseInt(tf.getText()); } catch (NumberFormatException e) { return 0; } } class Calc1L implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText(Integer.toString(bl.calculation1(getValue(t)))); } } class Calc2L implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText(Integer.toString(bl.calculation2(getValue(t)))); } } // If you want something to happen whenever // a JTextField changes, add this listener: class ModL implements DocumentListener { public void changedUpdate(DocumentEvent e) { } public void insertUpdate(DocumentEvent e) { bl.setModifier(getValue(mod)); } public void removeUpdate(DocumentEvent e) { bl.setModifier(getValue(mod)); } } public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(t); calc1.addActionListener(new Calc1L()); calc2.addActionListener(new Calc2L()); JPanel p1 = new JPanel(); p1.add(calc1); 14 p1.add(calc2); cp.add(p1); mod.getDocument().addDocumentListener(new ModL()); JPanel p2 = new JPanel(); p2.add(new JLabel("Modifier:")); p2.add(mod); cp.add(p2); } public static void main(String[] args) { Console.run(new Separation(), 250, 100); } } // /:~ You can see that BusinessLogic is a straightforward class that performs its operations without even a hint that it might be used in a GUI environment. It just does its job. Separation keeps track of all the UI details, and it talks to BusinessLogic only through its public interface. All the operations are centered around getting information back and forth through the UI and the BusinessLogic object. So Separation, in turn, just does its job. Since Separation knows only that it’s talking to a BusinessLogic object (that is, it isn’t highly coupled), it could be massaged into talking to other types of objects without much trouble. Thinking in terms of separating UI from business logic also makes life easier when you’re adapting legacy code to work with Java. Concurrency & Swing It is easy to forget that you are using threads when you program with Swing. The fact that you don’t have to explicitly create a Thread object means that threading issues can catch you by surprise. Typically, when you write a Swing program, or any GUI application with a windowed display, the majority of the application is event driven, and nothing really happens until the user generates and event by clicking on a GUI component with the mouse, or striking a key. Just remember that there is a Swing event dispatching thread, which is always there, handling all the Swing events in turn. This needs to be considered if you want to guarantee that your application won’t suffer from deadlocking or race conditions. This section looks at a couple of issues worth noting when working with threads under Swing. Runnable revisited Previously, I suggested that you think carefully before making a class as an 15 implementation of Runnable. Of course, if you must inherit from a class and you want to add threading behavior to the class, Runnable is the correct solution. The following example exploits this by making a Runnable JPanel class that paints different colors on itself. This application is set up to take values from the command line to determine how big the grid of colors is and how long to sleep( ) between color changes. By playing with these values, you’ll discover some interesting and possibly inexplicable features of threads: package c14; //: c14:ColorBoxes.java // Using the Runnable interface. // <applet code=ColorBoxes width=500 height=400> // <param name=grid value="12"> // <param name=pause value="50"></applet> // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import com.bruceeckel.swing.*; class CBox extends JPanel implements Runnable { private Thread t; private int pause; private static final Color[] colors = { Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.WHITE, Color.YELLOW }; private static Random rand = new Random(); private static final Color newColor() { return colors[rand.nextInt(colors.length)]; } private Color cColor = newColor(); public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(cColor); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); } public CBox(int pause) { this.pause = pause; t = new Thread(this); t.start(); } public void run() 16 { while (true) { cColor = newColor(); repaint(); try { t.sleep(pause); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class ColorBoxes extends JApplet { private boolean isApplet = true; private int grid = 12; private int pause = 50; public void init() { // Get parameters from Web page: if (isApplet) { String gsize = getParameter("grid"); if (gsize != null) grid = Integer.parseInt(gsize); String pse = getParameter("pause"); if (pse != null) pause = Integer.parseInt(pse); } Container cp = getContentPane(); cp.setLayout(new GridLayout(grid, grid)); for (int i = 0; i < grid * grid; i++) cp.add(new CBox(pause)); } public static void main(String[] args) { ColorBoxes applet = new ColorBoxes(); applet.isApplet = false; if (args.length > 0) applet.grid = Integer.parseInt(args[0]); if (args.length > 1) applet.pause = Integer.parseInt(args[1]); Console.run(applet, 500, 400); } } // /:~ 17 ColorBoxes is the usual applet/application with an init( ) that sets up the GUI. This configures a GridLayout so that it has grid cells in each dimension. Then it adds the appropriate number of CBox objects to fill the grid, passing the pause value to each one. In main( ) you can see how pause and grid have default values that can be changed if you pass in command-line arguments, or by using applet parameters. CBox is where all the work takes place. This is inherited from JPanel and it implements the Runnable interface so that each JPanel can also be a Thread. Remember that when you implement Runnable, you don’t make a Thread object, just a class that has a run( ) method. Thus, you must explicitly create a Thread object and hand the Runnable object to the constructor, then call start( ) (this happens in the constructor). In CBox this thread is called t. Notice the array colors, which is an enumeration of all the colors in class Color. This is used in newColor( ) to produce a randomly selected color. The current cell color is cColor. paintComponent( ) is quite simple; it just sets the color to cColor and fills the entire JPanel with that color. In run( ), you see the infinite loop that sets the cColor to a new random color and then calls repaint( ) to show it. Then the thread goes to sleep( ) for the amount of time specified on the command line. Precisely because this design is flexible and threading is tied to each JPanel element, you can experiment by making as many threads as you want. (In reality, there is a restriction imposed by the number of threads your JVM can comfortably handle.) 18 This program also makes an interesting benchmark, since it can and has shown dramatic performance and behavioral differences between one JVM threading implementation and another. Managing concurrency When you make changes to any Swing component properties from the main method of your class or in a separate thread, be aware that the event dispatching thread might be vying for the same resources. The following program shows how you can get an unexpected result by not paying attention to the event dispatching thread: package c14; //: c14:EventThreadFrame.java // Race Conditions using Swing Components. // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.Console; public class EventThreadFrame extends JFrame { private JTextField statusField = new JTextField("Initial Value"); public EventThreadFrame() { Container cp = getContentPane(); cp.add(statusField, BorderLayout.NORTH); addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { try { // Simulate initialization overhead Thread.sleep(2000); } catch (InterruptedException ex) { throw new RuntimeException(ex); } statusField.setText("Initialization complete"); } }); } public static void main(String[] args) { EventThreadFrame etf = new EventThreadFrame(); Console.run(etf, 150, 60); etf.statusField.setText("Application ready"); System.out.println("Done"); } } // /:~ 19 It is easy to see what is supposed to happen. In the main method, a new EventThreadFrame class is created and run using the Console.run( ) method. After the frame has been created and run, the value of the text field is set to “Application ready,” and then, just before exiting main( ), “Done” is sent to the console. When the frame is created, the text field is constructed with the value “Initial Value” in the constructor of the frame, and an event listener is added that listens for the opening of the window. This event will be received by the JFrame as soon as the setVisible(true) method has been called (by Console.run( )) and is the right place to do any initialization that affects the view of the window. In this example, a call to sleep( ) simulates some initialization code that might take a couple of seconds. After this is done, the value of the text box is set to “Initialization complete.” You would expect that the text field would display “Initial Value” followed by “Initialization complete” and then “Application Ready.” Next the word “Done” should appear on the command prompt. What really happens is that the setText( ) method on the TextField is called by the main thread before the EventThreadFrame has had a chance to process its events. This means that the string “Application ready” might actually appear before “Initialization complete.” In reality, things might not even appear in this order. Depending on the speed of your system, the Swing event dispatching thread may already be busy handling the windowOpened event, so you won’t see the text field value until after that event, but by then the text will have been changed to “Initialization Complete.” Since the text field was set to this value last, the message “Application ready” is lost. To makes things worse, the word “Done” appears on the command prompt before anything else happens at all! This undesirable and somewhat unpredictable effect is caused by the simple fact that there are two threads that need some sort of synchronization. It shows that you can sometimes get into trouble with threads and Swing. To solve this problem, you must ensure that Swing component properties are only ever updated by the event dispatch thread. This is easier than it sounds, using one of Swing’s two mechanisms, SwingUtilities.invokeLater( ) and SwingUtilities.invokeandWait( ). They do most of the work, which means that you don’t have to do too much complicated synchronization or thread programming. They both take runnable objects as parameters and drive the run( ) with the Swing event processing thread, after it has processed any pending events in the queue. package c14; //: c14:InvokeLaterFrame.java // Eliminating race Conditions using Swing Components. // From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002 // www.BruceEckel.com. See copyright notice in CopyRight.txt. import javax.swing.*; import java.awt.*; import java.awt.event.*; 20 import com.bruceeckel.swing.Console; public class InvokeLaterFrame extends JFrame { private JTextField statusField = new JTextField("Initial Value"); public InvokeLaterFrame() { Container cp = getContentPane(); cp.add(statusField, BorderLayout.NORTH); addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { try { // Simulate initialization overhead Thread.sleep(2000); } catch (InterruptedException ex) { throw new RuntimeException(ex); } statusField.setText("Initialization complete"); } }); } public static void main(String[] args) { final InvokeLaterFrame ilf = new InvokeLaterFrame(); Console.run(ilf, 150, 60); // Use invokeAndWait() to synchronize output to prompt: // SwingUtilities.invokeAndWait(new Runnable() { SwingUtilities.invokeLater(new Runnable() { public void run() { ilf.statusField.setText("Application ready"); } }); System.out.println("Done"); } } // /:~ A Runnable anonymous inner class is passed to SwingUtilities.invokeLater( ), which calls the setText( ) method of the text field. This queues the runnable object as an event so that it is the event dispatching thread that calls the setText( ) method after first processing any pending events. This means that the windowOpening event will be processed before the text field displays “Application ready,” which is the intended result. invokeLater( ) is asynchronous, so it returns right away. This can be useful because it doesn’t block, so your code runs smoothly. However, it doesn’t solve the problem with the “Done” string, which is still printed to the 21 command prompt before anything else happens. The solution to this problem is to use invokeAndWait( ) instead of invokeLater( ) to set the text field value to “Application Ready.” This method is synchronous, which means that it will block until the event has been processed before returning. The System.out.println(“Done”) statement will only be reached after the text field value has been set, so it will be the last statement to be executed. This gives us completely predictable and correct behavior. Using invokeAndWait( ) provides one of the necessary conditions for deadlock, so make sure that you are careful about controlling shared resources if you are using invokeAndWait( ), especially if you are calling it from more than one thread. You will probably use invokeLater( ) more often than invokeAndWait( ), but remember that if you set the properties of a Swing component any time after initialization, it should be done using one of these methods. 22