Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

Converting Numeric Entries and Displaying Multiline Text

 

Tech Tips Archive


WELCOME to the Core Java Technologies Tech Tips (formerly the Java Developer Connection (JDC) Tech Tips), July 23, 2002. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

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 John Zukowski, president of JZ Ventures, Inc.

Pixel

CONVERTING NUMERIC ENTRIES

Numbers entered from the console into a computer are not entered as their decimal value. Internally, a number entered from the keyboard is represented by a Unicode value of the numerical representation for the locale-specific character of that number. In other words, if an American user enters the number 1 on an English language system, the computer represents it as the Unicode value 49. The Unicode value is the the American-English, numerical representation of the character 1. This numerical representation then maps to a physical representation of the number 1, based on the font being used. At no point is this character 1 thought of as the numerical value of 1.

There are two mechanisms in the standard Java libraries that you can use to convert a user's "number" input into actual numeric values. Which mechanism you use depends on your personal preferences and desired feature support. For example, if you want to convert the user entry "3.14" to the value 3.14, there is a way to directly convert the string. Converting locale-specific input, such as "$5,000.12" to the value 5000.12, requires a little more work. You'll learn how to do both types of conversions in this tip.

To convert from a string to a number, use the wrapper classes. These classes offer parsing methods that do the conversion. Which method you use depends on the data type of the intended result:

  • String to byte -- Byte.parseByte(string)
  • String to short -- Short.parseShort(string)
  • String to int -- Integer.parseInt(string)
  • String to long -- Long.parseLong(string)
  • String to float -- Float.parseFloat(string)
  • String to double -- Double.parseDouble(string)

The integer types (byte, short, int, and long) also offer an ovloaded version of the associated parse method. The overloaded version includes an input base as a second argument to the method. So, for example, if the input is in base 2, you don't have to manually calculate that "100" is 4.

  • String to byte -- Byte.parseByte(string, base)
  • String to short -- Short.parseShort(string, base)
  • String to int -- Integer.parseInt(string, base)
  • String to long -- Long.parseLong(string, base)

The following program demonstrates these two conversion mechanisms. The program prompts for two numbers. When you run the program, and respond to the prompts, the first number you enter is converted to a double datatype. Be sure to try entering input with scientific notation, too, as in "1.3E4" to indicate 13000. The second prompt is for a hex value. Here, you can enter a hex value such as "FFFFFF" to indicate 16777215 in base 10.

import java.io.*;

public class Input {
  public static void main(String args[]) 
      throws IOException {
    InputStreamReader isr = 
      new InputStreamReader(System.in);
    BufferedReader input = new BufferedReader(isr);

    System.out.println("Enter a double: ");
    String doubleString = input.readLine();
    if (doubleString != null) {
      try {
        double d = Double.parseDouble(doubleString);
        System.out.println("Double: " + d);
      } catch (NumberFormatException e) {
        System.err.println("Bad input: " + 
                                         doubleString);
      }
    }

    System.out.println("Enter a hex value: ");
    String hexString = input.readLine();
    if (hexString != null) {
      try {
        long l = Long.parseLong(hexString, 16);
        System.out.println("Converted Hex: " + l);
      } catch (NumberFormatException e) {
        System.err.println("Bad input: " + hexString);
      }
    }
  }
}

Note: The previous example is for demonstration purposes only. Under no circumstances should a properly created internationalized program accept input from the user without using the NumberFormat capabilities described below. Also, in a similar way, under no circumstances should a text message be hard-coded into a println statement. You should use a ResourceBundle for maintaining locale-specific messages.

When you run the program, the interaction should look something like this:

    Enter a double: 
    1.3E4
    Double: 13000.0
    Enter a hex value: 
    FFFFFF
    Hex: 16777215

If the input cannot be converted, the program throws a NumberFormatException (which is indicated in this program by "Bad input:").

To convert formatted input, use the NumberFormat class of the java.text package. NumberFormat allows you to accept input in a locale-specific way, while dealing with such things as currency symbols, grouping separators (for example, the comma in the US or the period in Germany), decimal separators (for example, the period in the US or the comma in Germany), and percentages.

The NumberFormat class is abstract. It works by using the Factory design pattern. You provide the details of the format you want. NumberFormat then returns an instance of a subclass that implements the desired input pattern. By using the returned instance of NumberFormat, you can translate the requested input format.

NumberFormat provides a number of methods that return a specific format for the current default locale:

  • Numbers -- NumberFormat.getNumberInstance()
  • Currency -- NumberFormat.getCurrencyInstance()
  • Percentages -- NumberFormat.getPercentInstance()
  • Integers -- NumberFormat.getIntegerInstance()

If these methods don't return the format you want, you can define the format yourself by creating an instance of the DecimalFormat class.

If you want to support a locale other then the default, simply pass the locale as an argument to the overloaded versions:

  • Numbers -- NumberFormat.getNumberInstance(locale)
  • Currency -- NumberFormat.getCurrencyInstance(locale)
  • Percentages -- NumberFormat.getPercentInstance(locale)
  • Integers -- NumberFormat.getIntegerInstance(locale)

After you get the necessary instance, you call the parse method to translate the string to a number.

  NumberFormat format = 
    NumberFormat.getNumberInstance();
  Number number = format.parse(string);

You can also use NumberFormat to format how you want to display numbers to users. Instead of using the parse method, you use the format method.

  NumberFormat format = 
    NumberFormat.getCurrencyInstance();
  Number number = format.parse(string);
  System.out.println(format.format(number));

To demonstrate, the following program prompts for a double value and a currency amount. For the double value, you can enter an integer, floating point number, or use scientific notation. For the currency amount, and for the US locale used in the example program, you need to start with a currency symbol (such as $). However, the currency amount doesn't require a grouping separator or decimal point. These characters are displayed in the output in the appropriate places.

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

public class InputFormat {
  public static void main(String args[]) 
      throws IOException {
    InputStreamReader isr = 
      new InputStreamReader(System.in);
    BufferedReader input = new BufferedReader(isr);

    System.out.println("Enter a double: ");
    String doubleString = input.readLine();
    if (doubleString != null) {
      try {
        NumberFormat format = 
          NumberFormat.getNumberInstance(Locale.US);
        Number number = format.parse(doubleString);
        System.out.println("Double: " + 
          number.doubleValue());
      } catch (ParseException e) {
        System.err.println("Bad input: " + 
          doubleString);
      }
    }

    System.out.println("Enter a currency value: ");
    String currencyString = input.readLine();
    if (currencyString != null) {
      try {
        NumberFormat format = 
          NumberFormat.getCurrencyInstance(Locale.US);
        Number number = format.parse(currencyString);
        System.out.println("Currency: " + 
          format.format(number));
      } catch (ParseException e) {
        System.err.println("Bad input: " + 
          currencyString);
      }
    }
  }
}

Although the Input and InputFormat programs function similarly, there are some differences. The first difference is that parsing with NumberFormat throws a ParseException instead of a NumberFormatException. Another, and bigger, difference has to do with the way parsing is done. With the different wrapper methods, such as Integer.parseInt, parsing is attempted on the entire string. If the string as a whole is invalid, the parsing fails. That isn't how NumberFormat.parse works though. With NumberFormat, parsing fails (and a ParseException is thrown) only if there is an invalid leading character in the input.

To demonstrate this difference, enter "3.a" in response to the prompt for a double value. The Input program will throw an exception. By comparison, the InputFormat program will indicate that the input was 3.0.

    java Input
    Enter a double: 
    3.a
    Bad input: 3.a

    java InputFormat
    Enter a double: 
    3.a
    Double: 3.0

You can use NumberFormat to progressively parse through pieces of a string. There is a second version of NumberFormat.parse that accepts a second parameter indicating a parse position. You can use this version of the parse method to loop through a long string and pull out individual numbers from it. The position is indicated by the ParsePosition class, which automatically positions itself at the point where the parsing ends. If parsing fails due to an invalid character, the starting and ending position will be the same. There is no ParseException thrown by this form of the parse method.

The following program demonstrates progressively parsing through a string. The program prompts for set of numbers. Each number in the set must be separated by a single non-numeric character. The program then parses through the set, and displays the individual numbers:

import java.io.*;
import java.text.*;

public class InputPieces {
  public static void main(String args[]) 
      throws IOException {
    InputStreamReader isr = 
      new InputStreamReader(System.in);
    BufferedReader input = new BufferedReader(isr);

    System.out.println(
      "Enter space delimited set of numbers: ");
    String bulkString = input.readLine();
    if (bulkString != null) {
      NumberFormat format = 
        NumberFormat.getNumberInstance();
      ParsePosition position = new ParsePosition(0);
      int beginIndex;
      while ((beginIndex = position.getIndex()) 
          < bulkString.length()) {
        Number number = 
          format.parse(bulkString, position);
        System.out.println(
          "Number: " + number.doubleValue());
        int endIndex = position.getIndex();
        if (beginIndex == endIndex) {
          System.err.println(
            "Parsing error, position: " + endIndex);
        } else {
          position.setIndex(endIndex+1);
        }
      }
    }
  }
}

For input of "1e54,456/3E4 8" (where the separators are the e, /, and space) the output is:

    Number: 1.0
    Number: 54456.0
    Number: 30000.0
    Number: 8.0

Although the getXXXInstance methods of NumberFormat can meet most needs for formats to accept or display, it doesn't meet every format need. For those cases, where the getXXXInstance methods of NumberFormat can't meet the need, you can use the DecimalFormat class. When you call a method such as NumberFormat.getCurrencyInstance(), it creates a locale-specific version of DecimalFormat based on the type you request. Using DecimalFormat, you can also create a custom NumberFormat.

In general, creating a custom NumberFormat with DecimalFormat is done for output purposes. For example, suppose you want to display numbers with grouping separators (commas in the United States) every two positions instead of every three, and always with four digits after the decimal place. That would be a format of "#,#0.0000", where each '0' indicates that a number should be shown at the position. Each '#' also indicates that a number should be shown at the position, but not to show anything if the value is zero. The grouping separator automatically repeats to the left, so a display format of "##,##,#0.0000" is not necessary.

Here's a program that demonstrates using the DecimalFormat class to create a custom Number Format. The program prompts for a number and an output format. Try running the program with different numbers and different formatting characteristics.

import java.io.*;
import java.text.*;

public class InputPrompt {
  public static void main(String args[]) 
      throws IOException {
    InputStreamReader isr = 
      new InputStreamReader(System.in);
    BufferedReader input = new BufferedReader(isr);

    System.out.println("Enter number: ");
    String doubleString = input.readLine();

    if (doubleString != null) {
      try {
        NumberFormat format = 
          NumberFormat.getNumberInstance();
        Number number = format.parse(doubleString);

        System.out.println("Enter format: ");
        String formatString = input.readLine();

        if (formatString != null) {
          NumberFormat outputFormat = 
            new DecimalFormat(formatString);;
          System.out.println("Output: " + 
            outputFormat.format(number));
        }
      } catch (ParseException e) {
        System.err.println("Bad input: " + 
          doubleString);
      }
    }
  }
}

Here's an example of what you should see:

    Enter number:
    1234.56
    Enter format:
    #,#0.0000
    Output: 12,34.5600

For more information about using NumberFormat, see the Using Predefined Formats section of the Java Tutorial. Also, see the Customizing Formats section of the Java Tutorial for more information on using DecimalFormat.

Pixel

DISPLAYING MULTILINE TEXT

Suppose you want to create a label (either Swing JLabel or AWT Label) that displays multiple lines of text in a GUI. How would you create it? One way to do it is to create multiple labels, each containing one line of text, and group the labels in a vertical panel. Here's a program that does that:

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

public class Multi1 extends JFrame {
  public Multi1() {
    super("Vertical JLabel Set");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container content = getContentPane();
    BoxLayout layout = 
      new BoxLayout(content, BoxLayout.Y_AXIS);
    content.setLayout(layout);
    String lines[] = {"Line 1", "Line 2" , 
      "Line 3", "Line 4" , "Line 5"};
    for (int i=0, n=lines.length; i<n; i++) {
      content.add(new JLabel(lines[i]));
    }
    setSize(300, 200);
  }
  public static void main(String args[]) {
    JFrame frame = new Multi1();
    frame.show();
  }
}

If you run the Multi1 program, you should see the following:

multi 1

An easier approach to displaying multiline text in a Swing label is to use the HTML feature of Swing text components. By prefixing the text label of the component with <HTML>, the label switches to HTML display mode. Then, by using HTML tags, such as <BR> and <P>, or by simply using regular wordwrap at the end of a line, you can easily display a Swing label with multiple lines of text.

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

public class Multi2 extends JFrame {
  public Multi2() {
    super("HTML JLabel");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container content = getContentPane();
    String input = 
      "<HTML><P ALIGN=\"CENTER\">" +
      "You're A Grand Old Flag,<BR>" +
      "You're a high fly-ing flag,<BR>" +
      "And for-ev-er, in peace, may you wave.<BR>" +
      "You're the em-blem of<BR>" +
      "the land I love,<BR>" +
      "The home of the free and the brave.";

    JLabel label = new JLabel(input);
    content.add(label, BorderLayout.CENTER);
    setSize(300, 200);
  }
  public static void main(String args[]) {
    JFrame frame = new Multi2();
    frame.show();
  }
}

If you run the Multi2 program, you should see the following:

multi 2

Probably a simpler way to display multiple lines of text in a GUI is not with a label. Instead use a text area component. A text area component is specifically designed to support the display of multiple lines of text. Here's a program that uses a text area component to display multiple lines of text:

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

public class Multi3 extends JFrame {
  public Multi3() {
    super("Read-only JTextArea");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container content = getContentPane();
    String input = 
      "I pledge allegiance to the flag of " +
      "the United States of America, and " +
      "to the republic for which it stands, " +
      "one nation, under God, indivisible " +
      "with liberty and justice for all.";

    JTextArea text = new JTextArea(input);
    text.setEditable(false);
    text.setLineWrap(true);
    text.setWrapStyleWord(true);
/*    text.setBackground(
      (Color)UIManager.get("Label.background"));
    text.setForeground(
      (Color)UIManager.get("Label.foreground"));
    text.setFont(
      (Font)UIManager.get("Label.font"));
*/    JScrollPane pane = new JScrollPane(text);
    content.add(pane, BorderLayout.CENTER);
    setSize(300, 200);
  }
  public static void main(String args[]) {
    JFrame frame = new Multi3();
    frame.show();
  }
}

If you run the Multi3 program, you should see the following:

multi 3

Notice that both line wrap and word wrap style are enabled in the Multi3 program. By enabling both, you don't have to manually calculate what constitutes a line or worry about what font will be used by the HTML viewer.

There are advantages and disadvantages to using a JTextArea (or TextArea) for the display of multiline text. One disadvantage is that the display of the read-only text component looks different than the display of the label control. This is a minor disadvantage, and it's something you can fix. Uncomment the six lines of code in the middle of the Multi3 program to copy the foreground, background, and font setting over to the JTextArea. Recompile. Then run the program and see the difference. The JTextArea will now look more like a JLabel.

multi 3a

An advantage of using a text area component is that you do not have to calculate where each line breaks. Another advantage of the text area component approach, is that you can select the displayed message and copy the text to the system clipboard for further processing. This is something that you can't easily do with a label.

Swing components are usually sufficient for dealing with the display of multiline text, but not always. For example, if you want to create a custom editor, you'll probably need to draw the multiline output yourself. In cases like this, there are five classes available to assist you:

  • AttributedString
  • AttributedCharacterIterator
  • FontRenderContext
  • LineBreakMeasurer
  • TextLayout

These classes let you describe the text to draw, and measure how much text will fit on each line. Use the AttributedString class to contain the text. It allows different characters of the text string to have different attributes. However in the simplest case, you simply pass the quoted string to the constructor.

AttributedString attributedString = 
  new AttributedString("...");

To configure an attribute for the entire string, use the addAttribute method. For instance, to change the font for an entire string, specify:

  Font font = ...
  attributedString.addAttribute(
   TextAttribute.FONT, font);

To configure an attribute for just a part of the content, you pass in a beginning and ending index:

 attributedString.addAttribute(
   TextAttribute.FONT, font, startPosition, 
     endPosition);

The TextAttribute class of the java.awt.font package includes constants for the different attributes you can set. These include foreground and background colors, font, superscript, underline, justification, and many others.

You only need to configure the attributes of the string once. Then you need to draw the string. That's where the other classes and interfaces help.

When you need to draw the text, you fetch an AttributedCharacterIterator from the AttributedString so that the system can pull out the individual characters of the string.

  AttributedCharacterIterator characterIterator = 
    attributedString.getIterator();

FontRenderContext is used to ensure that the proper text sizes are used in the drawing. You obtain the FontRenderContext from the Graphics2D object passed into the paint method. You could call the constructor for FontRenderContext, however doing so wouldn't provide the correct measurements for the current graphic display.

  public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D)g;
    ...
    FontRenderContext fontRenderContext = 
      g2d.getFontRenderContext();

The LineBreakMeasurer class is used to determine how much text fits on a line. More specifically, given an AttributedCharacterIterator and FontRenderContext, LineBreakMeasurer measures how much of the AttributedString fits on each line. Each line is represented by a TextLayout.

  int screenWidth = ...;

  LineBreakMeasurer measurer = 
    new LineBreakMeasurer(characterIterator, 
      fontRenderContext);
  while (measurer.getPosition() < 
      characterIterator.getEndIndex()) {
    // Get line
    TextLayout textLayout = 
      measurer.nextLayout(screenWidth);
    ...
  }

That just leaves drawing the TextLayout, and there is a draw method for just that. Between lines, you just need to increase the y position to move down the screen.

  textLayout.draw(g2d, x, y);

Putting all the pieces together produces the following program:

import java.awt.*;
import javax.swing.*;
import java.awt.font.*;
import java.text.*;

public class Multi4 extends JFrame {
  String input = 
    "EDWARD by the grace of God, King of " +
    "England, Lord of Ireland, and Duke of " +
    "Guyan, to all Archbishops, Bishops, etc. " +
    "We have seen the Great Charter of the " +
    "Lord HENRY, sometimes King of England, " +
    "our father, of the Liberties of England, " +
    "in these words: ";
  AttributedString attributedString;

  public Multi4() {
    super("Manual Painting");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Font font = (Font)UIManager.get("Label.font");
    attributedString = new AttributedString(input);
    attributedString.addAttribute(
      TextAttribute.FONT, font);
    Color color = 
      (Color)UIManager.get("Label.foreground");
    attributedString.addAttribute(
      TextAttribute.FOREGROUND, color);
    setSize(300, 200);
  }

  public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2d = (Graphics2D)g;

    // Watch the margins
    Insets insets = getInsets();
    int width = getSize().width - 
      insets.right - insets.left;
    // Set the starting position to draw
    int x = insets.left;
    int y = insets.top;

    // Get iterator for string
    AttributedCharacterIterator characterIterator = 
      attributedString.getIterator();
    // Get font context from graphics
    FontRenderContext fontRenderContext = 
      g2d.getFontRenderContext();
    // Create measurer
    LineBreakMeasurer measurer = 
      new LineBreakMeasurer(characterIterator, 
        fontRenderContext);
    while (measurer.getPosition() < 
        characterIterator.getEndIndex()) {
      // Get line
      TextLayout textLayout = 
        measurer.nextLayout(width);
      // Move down to baseline
      y += textLayout.getAscent();
      // Draw line
      textLayout.draw(g2d, x, y);
      // Move down to top of next line
      y += textLayout.getDescent() + 
        textLayout.getLeading();
    }
  }

  public static void main(String args[]) {
    JFrame frame = new Multi4();
    frame.show();
  }
}

If you run the Multi4 program, you should see the following:

multi 4

For information about drawing more than text with the Java 2D API, see the tutorial Displaying Graphics with Graphics2D.

Pixel

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

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

SUBSCRIBE/UNSUBSCRIBE
- To subscribe, go to the subscriptions 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 JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html

- COPYRIGHT
Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

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

Core Java Technologies Tech Tips
July 23, 2002

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