.
.
Core Java
Technologies Technical Tips
.
   View this issue as simple text December 10, 2003    

In this Issue

Welcome to the Core Java Technologies Tech Tips for December 8, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-More About Handling Exceptions
-Using HTML in Swing Components

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

This issue of the Core Java Technologies Tech Tips is written by Daniel H. Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net.

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

.
.

MORE ABOUT HANDLING EXCEPTIONS

The November 24, 2003 tip titled "Handling Exceptions" demonstrated some of the things that can go wrong if you ignore or mishandle exceptions. The following tip continues that discussion. Unfortunately, there were two errors in the final code example, FileAccess, of the previous tip. The errors appeared in the version of the tip that was sent to subscribers. The example was corrected after the mailing, and the corrected version appears on the Web site. The following tip actually starts with that faulty code. It shows you how to correct the problems in the code so that it handles exceptions more robustly. This tip also covers some of the components of the exception mechanism that were not introduced in the previous tip.

The following sample program, BadException, highlights three problems. Two of them appeared in the FileAccess sample program in the previous tip. The first of the two (Problem 1 in the code) is the more serious of the problems, and could result in an unanticipated exception being thrown. The second of the problems (Problem 2) is an unnecessary use of the exception mechanism. Problem 3, which did not appear in the previous tip, includes a resource leak and other items that require more explanation of the exception mechanism. Take a moment to consider what is wrong in each case and how you might fix it.

   import java.io.FileReader;
   import java.io.FileNotFoundException;
   import java.io.File;
   import java.io.BufferedReader;
   import java.io.IOException;

   public class BadException {

      private static String fileName;

      public static void main(String[] args) {
        FileReader fileReader = null;
        try {
          fileReader = new FileReader(getFile(args));
        } catch (FileNotFoundException e) {
          System.out.println(
            "Could not locate a file: " +
            e.getMessage());
        }

        //  problem 1: do not do the following

        readFile(fileReader);
      }

      private static File getFile(String[] args)
        throws FileNotFoundException {

        // problem 2: do not do the following

        try {
          fileName = args[0];
        } catch (ArrayIndexOutOfBoundsException e) {
          throw new FileNotFoundException(
            "correct usage: " +
            "java FileAccess <pathToFile>");

        }
        File aFile = new File(fileName);
        if (aFile.exists()) {
          return aFile;
        } else {
          throw new FileNotFoundException(
            "There is no file at " + fileName);
        }
      }

      private static void readFile(
                               FileReader fileReader) {
   
     // problem 3: items not covered last time

        BufferedReader buffReader =
          new BufferedReader(fileReader);
        String temp = "";
        try {
          System.out.println(
            "== Beginning of the file ==");
          while (
            (temp = buffReader.readLine()) != null) {
              System.out.println(temp);
          }
        } catch (IOException e) {
          System.out.println(
            "There was a problem reading:"
            + fileName);
        }
        System.out.println(
          "==    End of the file    ==");
      }
   }

After compiling BadException.java, run it two ways. First, run it without passing in a command line, as follows:

  java BadException

The output should look something like this:

  Could not locate a file: correct usage: 
    java FileAccess <pathToFile>
  Exception in thread "main" 
    java.lang.NullPointerException
        at java.io.Reader.<init>(Reader.java:61)
        at java.io.BufferedReader.<init>
          (BufferedReader.java:76)
        at java.io.BufferedReader.<init>
          (BufferedReader.java:91)
        at BadException.readFile(BadException.java:50)
        at BadException.main(BadException.java:23)
Process terminated with exit code 1

Next, run it and pass in the name of a file that does not exist. For example:

  java BadException phantomFile.txt

This time you should see output that looks something like this:

  Could not locate a file: There is no file at 
    phantomFile.txt
  Exception in thread "main" 
    java.lang.NullPointerException
        at java.io.Reader.<init>(Reader.java:61)
        at java.io.BufferedReader.<init>
          (BufferedReader.java:76)
        at java.io.BufferedReader.<init>
          (BufferedReader.java:91)
        at BadException.readFile(BadException.java:50)
        at BadException.main(BadException.java:23)
Process terminated with exit code 1

In each case a NullPointerException was thrown by the readFile() method. In the first case, the getFile() method caught an ArrayIndexOutOfBoundsException, and threw a new FileNotFoundException with a message about correct usage. In the second case, the usage was correct but no such file existed. The FileNotFoundException was caught in main(), and the correct error message was issued about not being able to locate the file.

The problem is that the call to readFile() is not in the same try block as where the code attempts to initialize the FileReader. When an exception is thrown trying to create this resource, your code should then be prevented from using the resource. In both cases, the NullPointerException is thrown because FileReader was never instantiated and the fileReader variable is still null.

As a first step to solving the problem, you can move the call to readFile() into the try block in the main() method:

   public static void main(String[] args) {
     FileReader fileReader = null;  //a
     try {
      fileReader = new FileReader(getFile(args)); //b
       readFile(fileReader);  //c
     } catch (FileNotFoundException e) {
       System.out.println("Could not locate a file: " +
         e.getMessage());
     }
   }

This addresses problem 1, and the code behaves exactly as intended. If you execute the updated code:

   java BadException

you should see:

   Could not locate a file: correct usage: 
     java FileAccess <pathToFile>

Similarly, if you execute the updated code and specify the non-existent file:

  java BadException phantomFile.txt

you should see:

   Could not locate a file: There is no file 
     at phantomFile.txt

Although the code behaves correctly, there is still more cleaning up you can do. Now that fileReader is only accessed in the try block, you no longer have to declare and initialize it outside of the try block. You can eliminate the line marked as line a, and add FileReader to the front of line marked as line b. This declares and initializes the FileReader inside the try block. Also, there is no need to create and initialize the variable fileReader -- it is only used as a parameter for the readFile() method called in the next line. So you can eliminate line b and then move what's on the right side of the equals sign in line b in place of fileReader in line c. After you make those changes, the main() method looks like this:

   public static void main(String[] args) {
      try {
        readFile( new FileReader(getFile(args)));
      } catch (FileNotFoundException e) {
        System.out.println("Could not locate a file: " +
          e.getMessage());
      }
   }

Next, turn to problem 2. The goal of the getFile() method is to create a File object from the file name that is passed in as the only entry in the array named args. Two checks are made: was a file path passed in, and does it correspond to an actual file. Notice the differences in how these checks are implemented in the faulty code.

   private static File getFile(String[] args)
      throws FileNotFoundException {

      // check one: was a file path passed in
      // inefficient code and bad use of exceptions

      try {
        fileName = args[0];
      } catch (ArrayIndexOutOfBoundsException e) {
        throw new FileNotFoundException(
          "correct usage: " +
          "java FileAccess <pathToFile>");
  
      // check two: does the name correspond 
      // to an actual file

      File aFile = new File(fileName);
      if (aFile.exists()) {
        return aFile;
      } else {
        throw new FileNotFoundException(
          <There is no file at > + fileName);
      }
   }

The first check does not need the expensive machinery of an exception. You can simply check the length of the args array. If it the length is any value other than one, then the application is not being used correctly. In that case, a FileNotFoundException can be thrown so that the appropriate usage message is displayed. If the length is one, you can create a new File object with args[0] passed in as the path name. You can now perform the second check. Note that the use of the fileName variable has been replaced by a call to aFile.getName(). Here is a corrected version of getFile().

   private static File getFile(String[] args)
      throws FileNotFoundException {

      if (args.length != 1) {    // check one
        throw new FileNotFoundException(
          "correct usage:"
          + " java FileAccess <pathToFile>");
      }
      File aFile = new File(args[0]);

      if (aFile.exists()) {      // check two
        return aFile;
      } else {
        throw new FileNotFoundException(
          "There is no file at " + aFile.getName());
      }
   }

Next, turn your attention to the readFile() method. Given the name, you would expect readFile() to take a File object and not an instance of FileReader as a parameter. The problem is that the FileReader constructor can throw a FileNotFoundException. By creating an instance of FileReader in the main() method, you know it is being handled in the try block. You do not want to duplicate your efforts in readFile(). This is particularly true because you have handled every anticipated problem in the getFile() method. Here is the current state of readFile():

   private static void readFile(FileReader fileReader) { //a
      BufferedReader buffReader =
        new BufferedReader(fileReader);  //b
      String temp = "";                  //c
      try {
        System.out.println("== Beginning of the file ==");
        while ((temp = buffReader.readLine()) != null) {
          System.out.println(temp);
        }
      } catch (IOException e) {
        System.out.println("There was a problem reading:" //d
                            + fileName);                  //e
      }
      System.out.println("==    End of the file    =="); //f
                                           //g
   }

You can change line a to accept a reference to a File object instead of a FileReader, and declare that the readFile() method throws a FileNotFoundException. This requires a corresponding change in the main() method. The line:

   readFile( new FileReader(getFile(args)));

is changed to;

   readFile( getFile(args));

Change line b so that the BufferedReader constructor is passed in the FileReader that is created from the File object. So line b becomes:

  new BufferedReader( new FileReader(file));

If the FileReader constructor throws a FileNotFoundException, there is nothing useful that a user can do. So this exception bubbles back up to the main() method. Lines c, d, and e can move inside of the try block. What remains is a small change to line f and an addition at line g. For line f, instead of sending the error message to standard out, send it to the standard error output stream by changing the call:

  
   System.out.println("some string");

to:

   System.err.println("some string");

It is possible that a configuration has redirected the standard output stream to a file or other device. The standard err output stream should be displayed so that it can be monitored. You should send your exception and error messages to System.err. While you are thinking about this, change the println() statement in the main() method to send the output to standard error.

At g you need to add a finally block. The remaining problem is that you have opened a BufferedReader that needs to be closed whether or not it was possible to read the entire file. In other words, whether you make it all the way through the try block or whether you catch an IOException, you still need to try to clean up your resources. You can first try this.

   try { //...
   } catch { //...
   } finally {
      buffReader.close();
   }

The problem is that the close() method in the BufferedReader class can throw an IOException and so you need to surround this call with try and catch. Even inside of a finally block you need to handle potential exceptions.

Here is an updated version of the program, incorporating all of the changes described in this tip:

   import java.io.FileReader;
   import java.io.FileNotFoundException;
   import java.io.File;
   import java.io.BufferedReader;
   import java.io.IOException;

   public class ExceptionExample {

      public static void main(String[] args) {
        try {
          readFile(getFile(args));
        } catch (FileNotFoundException e) {
          System.err.println(
            "Could not locate a file: " 
            + e.getMessage());
        }
      }

      private static File getFile(String[] args)
        throws FileNotFoundException {

        if (args.length != 1) {
          throw new FileNotFoundException(
            "correct usage:"
            + " java FileAccess <pathToFile>");
        }
        File aFile = new File(args[0]);
        if (aFile.exists()) {
          return aFile;
        } else {
          throw new FileNotFoundException(
            "There is no file at " + aFile.getName());
        }
      }

      private static void readFile(File file)
                          throws FileNotFoundException {
        BufferedReader buffReader =
          new BufferedReader(new FileReader(file));

        try {
          String temp = "";
          System.out.println(
            "==Beginning of the file==");
          while (
            (temp = buffReader.readLine()) != null) {
              System.out.println(temp);
          }
          System.out.println(
            "==   End of the file   ==");
        } catch (IOException e) {
          System.err.println(
            "There was a problem reading:"
            + file.getName());
        } finally {
          try {
            buffReader.close();
          } catch (IOException e) {
            System.err.println("There was an error " +
              "cleaning up resources.");
          }
        }
      }
   }

For more information about handling exceptions, see Catching and Handling Exceptions in the Java Tutorial.

.
.

USING HTML IN SWING COMPONENTS

Many of the Swing components support the display of HTML. This tip shows you how to use HTML to add superscripts, to style, and to provide flexible line breaking in text added to Swing components such as JLabels and JButtons. There is surprisingly little that you need to do to achieve more flexible results in the labeling of your Swing components.

Let's start with four JButtons with labels "1st", "2nd", "3rd", and "4th". You can produce the JButtons in the following program:

   import javax.swing.JFrame;
   import javax.swing.JButton;
   import java.awt.GridLayout;

   public class TextButtons extends JFrame{

      TextButtons(){
        super("Text Buttons");
        setSize(400,200);
        getContentPane().setLayout(new GridLayout(1,4));
        getContentPane().add(new JButton("1st"));
        getContentPane().add(new JButton("2nd"));
        getContentPane().add(new JButton("3rd"));
        getContentPane().add(new JButton("4th"));
      }


      public static void main(String[] args) {
           new TextButtons().setVisible(true);
      }
   }

Here is the result of running TextButtons.

textbuttons

Although the buttons display in the correct order and with the correct labels, they might look better if the letters after the numbers (st, nd, rd, and th) were rendered as superscripts. You can do that by enclosing the letters in HTML <sup> tags. You need to begin each input string with an <html> tag, and end it with an </html> tag. You do not need to include the HTML head and body tags. Here is the code for introducing the superscripts (although <p> tags are used in this example, they're not required):

   import javax.swing.JFrame;
   import javax.swing.JButton;
   import java.awt.GridLayout;

   public class HTMLButtons extends JFrame{

      HTMLButtons(){
        setSize(400,200);
        getContentPane().setLayout(new GridLayout(1,4));
        getContentPane().add(new JButton(
                 "<html><p>1<sup>st</sup></p></html>"));
        getContentPane().add(new JButton(
                 "<html><p>2<sup>nd</sup></p></html>"));
        getContentPane().add(new JButton(
                 "<html><p>3<sup>rd</sup></p></html>"));
        getContentPane().add(new JButton(
                 "<html><p>4<sup>th</sup></p></html>"));
      }


      public static void main(String[] args) {
        new HTMLButtons().setVisible(true);
      }
   }

As you can see in this screenshot, you now have superscripts on each of the buttons.

HTMLButtons

Although HTML is most often used in Swing components to style text in a particular way, it can be used with care to handle long lines of text. If you have text that is too long for your JButton, the text that can fit on the button will appear followed by an ellipsis. One solution is to manually add breaks to the code using "\n". These newline characters are ignored in displaying the JButton text. They are respected in JTextAreas, but this is not a good solution even in that in that case because the breaks are now hardcoded. This might look good at the initial size you specify, but the breaks might not be appropriate for larger or smaller sizes.

You can use HTML to get multiple lines in a JButton. You can explicitly use a <br> tag to force a new line. However, for the reasons just mentioned, you're probably better off allowing the text to break automatically, based on the size of the button. This technique should be used with care because a JButton with a really long line of HTML text will have a very wide preferred size, which will affect calls to pack. The next example illustrates the four cases just discussed:

  • A button with text that is too long for the display
  • A button with text that is too long, but that attempts to use the \n character to force a new line
  • A text area where the new line characters are respected
  • A a button with HTML formatted text
   import javax.swing.JFrame;
   import javax.swing.JButton;
   import javax.swing.JTextArea;
   import java.awt.GridLayout;

   public class LongNames extends JFrame{

      LongNames(){
        setSize(400,200);
        getContentPane().setLayout(new GridLayout(1,4));
        getContentPane().add(new JButton(
            "This string is longer than the display."));
        getContentPane().add(new JButton(
                "This string \n is longer \n than the" +
                                         "\n display"));
        getContentPane().add(new JTextArea(
                "This string \n is longer \n than the" +
                                         "\n display"));
        getContentPane().add(new JButton(
              "<html> This string is longer than the " +
                                   "display. </html>"));
      }


      public static void main(String[] args) {
        new LongNames().setVisible(true);
      }
   }

Here is the result of running this code. If you make the JFrame bigger you will see the line breaks in the final JButton change accordingly.

LongNames

The HTML support for Swing components is still fairly basic. The documentation is clear that HTML 3.2 is targeted. This support should be sufficient for most needs when displaying small amounts of HTML on JButtons and JLabels. You can experiment with some of the standard tags to customize your applications in various ways. In the following example, the superscripts are used inside of a top level heading. Each button also includes a horizontal rule and colored text. One button includes bolded text and another is underlined.

   import javax.swing.JFrame;
   import javax.swing.JButton;
   import java.awt.GridLayout;

   public class MoreHTMLButtons extends JFrame{

      MoreHTMLButtons(){
        setSize(300,200);
        getContentPane().setLayout(new GridLayout(1,3));
        getContentPane().add(new JButton(
                       "<html><h1>1<sup>st</sup></h1>" +
            "<hr> <p color=blue> Use Blue</p></html>"));
        getContentPane().add(new JButton(
                       "<html><h1>2<sup>nd</sup></h1>" +
        "<hr> <p color=red>Use <b>Red</b></p></html>"));
        getContentPane().add(new JButton(
                   "<html><h1>3<sup>rd</sup></h1><hr>" +
        "<p color=green> Use <u>Green</u></p></html>"));
      }


      public static void main(String[] args) {
        new MoreHTMLButtons().setVisible(true);
      }
   }

The new customized buttons look like this.

morehtmlbuttons

It would be much easier if you could use a style sheet with your HTML for the various Swing components. For example, you could create a style sheet file named jbutton.css with the following contents:

   p {
   color: red;
   }

In a standard HTML document you could provide a link to this style sheet with a command like this.

   <link href="./jbutton.css" rel="stylesheet" type="text/css">

If you try to insert this code into the HTML used to instantiate your JButton, the URL will not know how to resolve the link. You will not get the expected behavior because your JButton will not be able to find the file jbutton.css. Instead, specify the location of the style sheet by calling getResource() and pass in the name of the file jbutton.css. Here is an example of using a style sheet to color the text on the middle button:

   import javax.swing.JFrame;
   import javax.swing.JButton;
   import java.awt.GridLayout;

   public class StyleSheets extends JFrame {

      private static final String HTML_HEAD = "<head>"   +
        "<link rel=STYLESHEET TYPE=\"text/css\" HREF=\"" +
        StyleSheets.class.getResource("jbutton.css")     +
        "\"></head>";

      StyleSheets() {
        setSize(300, 200);
        getContentPane().setLayout(new GridLayout(1, 3));
        getContentPane().add(new JButton(
          "<html><h1>1<sup>st</sup></h1>" +
          "<hr> <p color=blue> Use Blue</p></html>"));

        getContentPane().add(new JButton( "<html>" +
          HTML_HEAD +       // reference the style sheet
          "<h1>2<sup>nd</sup></h1>" +
          "<hr> <p>Use <b>Red</b></p></html>"));

        getContentPane().add(new JButton(
          "<html><h1>3<sup>rd</sup></h1><hr>" +
          "<p color=green> Use <u>Green</u></p></html>"));
      }


      public static void main(String[] args) {
        new StyleSheets().setVisible(true);
      }
   }

When you run this sample you will see that the first button and third buttons are colored appropriately using HTML. The second button calls a .css file, and uses it to set the color. The result is the same as it was when you ran MoreHTMLButtons.

Here is the result of running this code.

StyleSheets

.
.
Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developers.sun.com/dispatcher.jsp?uid=6910008


Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

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

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


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


Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


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


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.