# Formatted Text Fields

This document outlines the Swing implementation of formatted text fields. Formatted text fields provide a way for developers to specify the legal set of characters that can be input into a text component. This document is arranged in the following sections:

- Using JFormattedTextField
- How JFormattedTextField Works
- Filtering Input
- Filtering Navigation
- JFormattedTextField.AbstractFormatter
- JFormattedTextField.AbstractFormatterFactory
- DefaultFormatter
- AbstractFormatter implementations
- java.text.Format changes
- JavaDoc

## Using JFormattedTextField

`JFormattedTextField` will allow formatting of dates, numbers, Strings and arbitrary Objects. `JFormattedTextField` will rely on the `java.text.Format` classes to handle formatting of dates and numbers. To create a `JFormattedTextField` to input dates in the current locale-specific format:

        new JFormattedTextField(new Date());

If you need to display the dates in a specific format, you can use one of the `SimpleDateFormat` constructors:

        new JFormattedTextField(new SimpleDateFormat("MM/dd/yy"));

Numbers will be handled by an instance of `java.text.NumberFormat`. The following shows several ways to create a `JFormattedTextField` to edit numbers.

       new JFormattedTextField(new Number(1000));
       new JFormattedTextField(new DecimalFormat("#,###"));
       new JFormattedTextField(new DecimalFormat("0.###E0"));

`JFormattedTextField` will also support editing of strings given a mask that specifies what the legal characters are at a given character position. To create a `JFormattedTextField` to edit US phone numbers the following could be used:

       new JFormattedTextField(new MaskFormatter("(###) ###-####"));

## How JFormattedTextField Works

`JFormattedTextField` itself exposes a minimal amount of API in addition to that of its super class, `JTextField`. In its simplest terms, `JFormattedTextField` can be thought of as a `JTextField` with an additional value property (of type Object) and an object to handle the formatting (instance of `AbstractFormatter`).

As the value property is of type `Object`, it is necessary for the developer to cast the return type based on how the `JFormattedTextField` has been configured. The following shows a typical scenario of using a `JFormattedTextField` with dates:

      JFormattedTextField ftf = new JFormattedTextField();
      ftf.setValue(new Date());
      ...
      Date date = (Date)ftf.getValue();

A typical session for editing numbers looks like:

      JFormattedTextField ftf = new JFormattedTextField();
      ftf.setValue(new Integer(1000));
      ...
      int intValue = ((Number)ftf.getValue()).intValue();

## Filtering Input

Constraining the input into a text component previously required creating a subclass of `Document`. This is a rather heavy operation for such a simple, and common, usage. To make this task easier, we have created a class, `DocumentFilter`, that can be plugged into a `Document` (`Document` is an interface, which has not changed, instead `AbstractDocument` now has a setter/getter for a `DocumentFilter`, and a property is set for `Document`s that do not descend from `AbstractDocument` so that others can support `DocumentFilter` should they want to). When a `Document` with a `DocumentFilter` is messaged to remove or insert content, the `Document` invokes the corresponding method on the `DocumentFilter`. It is the `DocumentFilter`'s responsibility to issue a callback if the operation should proceed. In this manner the `DocumentFilter` has total control over how the `Document` can be mutated. `DocumentFilter` is defined by:

      public void remove(FilterBypass fb, int offset, int length) throws BadLocationException;
      public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException;
      public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException;

If the `DocumentFilter` wants to mutate the `Document` from inside the `remove` or `insertString` methods, it should either invoke `super`'s implementation, or invoke the method on the `FilterBypass`. Invoking the method on `super` or `FilterBypass` provides a way to circumvent the filter so that the caller doesn't get stuck in stack recursion. The `DocumentFilter` is not limited to only invoking one method back on the `FilterBypass`. It can invoke any of the methods exposed by `FilterBypass`, and can invoke them as many times as it wishes (within the scope of one of `DocumentFilter`'s methods). `FilterBypass` is defined by:

      public abstract Document getDocument();
      public abstract void remove(int offset, int length) throws BadLocationException;
      public abstract void insertString(int offset, String string, AttributeSet attr) throws BadLocationException;
      public abstract void replace(int offset, int length, String string, AttributeSet attr) throws BadLocationException;

The following example illustrates creating a `DocumentFilter` that maps lower case to upper case letters:

       DocumentFilter upperDF = new DocumentFilter() {
          public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
              super.insertString(fb, offset, string.toUpperCase(), attr);
          }

          public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException {
              if (string != null) {
                  string = text.toUpperCase();
              }
              super.insertString(fb, offset, string, attr);
          }
       };

## Filtering Navigation

Similar to `DocumentFilter`, a new class, `NavigationFilter`, allows for filtering where the selection can be placed. `NavigationFilter` is called by the navigation actions (such as left, right, center) to determine the next position to place the selection from a given position. `NavigationFilter` is a property on `JTextComponent`, and is defined by:

        public void setDot(FilterBypass fb, int dot, Position.Bias bias);
        public void moveDot(FilterBypass fb, int dot, Position.Bias bias);
        public int getNextVisualPositionFrom(JTextComponent text, int pos, Position.Bias bias, int direction, Position.Bias[] biasRet) throws BadLocationException;

Note that `getNextVisualPositionFrom` is defined in `View`; for consistency, the method in `NavigationFilter` is named the same.

Similar to `DocumentFilter`, `NavigationFilter` is passed a `FilterBypass` that should be invoked to handle the actual mutation. `NavigationFilter.FilterBypass` is defined by:

        public abstract Caret getCaret();
        public abstract void setDot(int dot, Position.Bias bias);
        public abstract void moveDot(int dot, Position.Bias bias);

## JFormattedTextField.AbstractFormatter

As previously mentioned, an instance of `AbstractFormatter` is used to format a particular Object value. `AbstractFormatter` can also impose an editing policy by defining a `DocumentFilter`; it can also impose a navigation policy by defining a `NavigationFilter`. `AbstractFormatter` is defined by:

        public void install(JFormattedTextField ftf);
        public void uninstall();
        public abstract Object stringToValue(String text) throws ParseException;
        public abstract String valueToString(Object value) throws ParseException;
        protected JFormattedTextField getFormattedTextField();
        protected void setEditValid(boolean valid);
        protected void invalidEdit();
        protected Action[] getActions();
        protected DocumentFilter getDocumentFilter();
        protected NavigationFilter getNavigationFilter();

Once `JFormattedTextField` is ready to use an `AbstractFormatter` it invokes `install`. `AbstractFormatter.install` performs the following:

1. Sets the text of the `JFormattedTextField` to the return value of `valueToString` (if a `ParseException` is thrown, an empty String is used and `setEditValid(false)` is invoked).
2. Installs the `DocumentFilter` returned from `getDocumentFilter` onto the `JFormattedTextField`'s `Document`.
3. Installs the `NavigationFilter` returned from `getNavigationFilter` onto the `JFormattedTextField`.
4. Installs the `Action`s returned from `getActions` onto the `JFormattedTextField`'s `ActionMap`.

Subclasses will typically only override `install` if they need to install additional `Listener`s beyond the `DocumentFilter` and `NavigationFilter`, or perhaps place the caret at an initial location.

Some `AbstractFormatter`s allow the `JFormattedTextField` to contain an invalid value when editing. To allow the `JFormattedTextField` to provide an indication of this, the `AbstractFormatter` should invoke `setEditValid(false)` when the user enters an invalid value, and then invoke `setEditValid(true)` when the value is valid again.

When `JFormattedTextField` is done with an `AbstractFormatter`, it invokes `uninstall`. `uninstall` removes the previously installed `Listener`s.

## JFormattedTextField.AbstractFormatterFactory

`JFormattedTextField` delegates the creation of `AbstractFormatter`s to an instance of `AbstractFormatterFactory` (a public static inner class of `JFormattedTextField`). This makes it easy for developers to provide different formatters for different states of the `JFormattedTextField`. For example, you could provide a special `AbstractFormatter` if the current value is `null`, or one formatter when editing and another when displaying. `AbstractFormatterFactory` is defined by:

        public abstract AbstractFormatter getFormatter(JFormattedTextField ftf);

If the developer has not supplied an `AbstractFormatterFactory`, one will be created based on the `Class` of the value.

## DefaultFormatter

`DefaultFormatter` extends `JFormattedTextField.AbstractFormatter` and is the superclass for all of the formatter implementations we provide. `DefaultFormatter` formats Objects using `toString` and creates the Object using the constructor that takes a String. `DefaultFormatter` allows a number of configuration options:

Option | Description
CommitsOnValidEdit | Determines when edits are published back to the `JFormattedTextField`. If `true`, `commitEdit` is invoked on the `JFormattedTextField` after every successful edit, otherwise `commitEdit` is invoked only when return is pressed.
OverwriteMode | Configures the behavior when inserting characters. If `overwriteMode` is `true` (the default), new characters overwrite existing characters in the model as they are inserted.
AllowsInvalid | Determines whether the value being edited is allowed to be invalid. It is often convenient to allow the user to enter invalid values until a commit is attempted.

## AbstractFormatter implementations

The following table lists the `AbstractFormatter` implementations that we provide, as well as intended use:

AbstractFormatter | Object Type | Notes
DefaultFormatter | Object | `valueToString` uses `Object.toString()`, and `stringToValue` uses the single argument constructor that takes a String.
MaskFormatter | Strings | Behavior is dictated by a per character mask that specifies legal values (e.g. "###-####").
InternationalFormatter | Objects | Uses an instance of `java.text.Format` to handle `valueToString` and `stringToValue`.
NumberFormatter | Numbers | Uses an instance of `NumberFormat` to handle formatting, descends from `InternationalFormatter`.
DateFormatter | Date | Uses an instance of `DateFormat` to handle formatting, descends from `InternationalFormatter`.

## java.text.Format changes

The Swing support for formatted dates and numbers made extensive use of the `Format` classes in the `java.text` package. The following problems were encountered using the previous API:

- You could only get the limits of one field at a time. If you wanted more, you had to invoke `format` repeatedly.
- The previous field constants were not sufficient to describe the entire text of the formatted strings. One glaring omission was the exponential part of formatted numbers.
- Using integer IDs for a set of constants that pretended to be extensible wasn't very elegant. When new constants were added, there could potentially be a conflict with any `Format` subclasses out there that we didn't know about. Note that the constants for `DateFormat` and `NumberFormat` already overlap, so a `NumberFormat` would have happily interpreted `YEAR_FIELD` as `FRACTION_FIELD`. The problem was exacerbated by the existence of the polymorphic `Format.format(Object, StringBuffer, FieldPosition)` method.
- The specification didn't define the behavior if a field didn't exist in a formatted string, and the actual results varied between different releases.
- The specification didn't define the behavior if a field wasn't recognized by a formatter; for example, if I asked a `NumberFormat` for the `MONTH_FIELD` (the actual implementation of this case returned 0 for both begin index and end index).
- Because of that, a caller could not find out which fields a `Format` subclass actually supported unless it knew the specific subclass. This, along with the fact that not all characters in a formatted string are part of fields, makes the implementation of a generic method that returns all fields impossible.

These issues have largely been addressed by adding the following method to `java.text.Format`:

        public AttributedCharacterIterator formatToCharacterIterator(Object obj);

Each `Format` class uses a type safe enumeration for the constants it supports.

## JavaDoc

### New Classes

The following classes are new to the 1.4 release:

- JFormattedTextField
- DocumentFilter
- NavigationFilter
- JFormattedTextField.AbstractFormatter
- JFormattedTextField.AbstractFormatterFactory
- DefaultFormatterFactory
- DefaultFormatter
- MaskFormatter
- InternationalFormatter
- Number Formatter
- DateFormatter

### Constants are renamed to conform to Java naming conventions

The bugtraq report that corresponds to this change is: 4468474.

The following constants have been replaced to conform Java to naming conventions:

- `CommitValueOnFocusLost` has been replaced with `COMMIT`.
- `CommitOrRevertValueOnFocusLost` has been replaced with `COMMIT_OR_REVERT`.
- `RevertValueOnFocusLost` has been replaced with `REVERT`.
- `PersistValueOnFocusLost` has been replaced with `PERSIST`.