Regular Expression Based AbstractFormatter
The 1.4 release of Java 2
Standard Edition introduced the JFormattedTextField
component. This is a flavor of JTextField
that allows you to constrain the text that may be input. Out of
the box, JFormattedTextField provides support for editing:
An additional feature in the Java 2 Standard Edition v 1.4 release
is support for regular
expressions. Regular expressions, among other things, provide
a means to specify the legal values that a set of characters, typically
a String, may accept. For example, to specify a U.S. phone number
of the format (xxx)xxx-xxxx, you could use the regular expression:
\(\d{3}\)\d{3}-\d{4}. A combination of the regular
expression support and JFormattedTextField are a natural
match, and this article discusses a way to combine the two.
JFormattedTextField uses an AbstractFormatter
to enforce the editing policy. As regular expressions are a way
of enforcing an editing policy, a subclass of AbstractFormatter
needs to be created to interact with the regular expression package.
This has been provided with the example class RegexFormatter.
In its simplest terms, a JFormattedTextField
is a JTextField that has a value of type Object.
The value varies depending upon what is being edited. For example,
for dates the value would be an instance of Date; for
numbers it might be an instance of Integer. JFormattedTextField
uses an AbstractFormatter to convert between this value
and a String representation using the valueToString
method, and from the String representation back to
an Object with the stringToValue method.
Since RegexFormatter merely enforces an editing policy
and doesn't necessarily provide any mapping between the String
and Object, it only needs to override the stringToValue
method. To enforce the regular expression, RegexFormatter
would override valueToString and use a Pattern
to determine if the String is legal, such as the following
implementation:
public Object stringToValue(String text) throws ParseException {
Pattern pattern = getPattern();
if (pattern != null) {
Matcher matcher = pattern.matcher(text);
if (matcher.matches()) {
return super.stringToValue(text);
}
throw new ParseException("Pattern did not match", 0);
}
return text;
}
|
If the value is legal, matcher.matches() returns
true, and super's implementation of stringToValue converts
the String back to an arbitrary Object.
If the String does not match the regular expression,
an exception is thrown indicating the value isn't legal.
Because RegexFormatter extends DefaultFormatter,
it gets the following for free:
- when to commit edits
- the
Class that stringToValue should
create
- support for overwrite mode
Using RegexFormatter
There are a number of ways to use RegexFormatter
with a JFormattedTextField. The simplest is shown in
the following code:
new JFormattedTextField(new RegexFormatter("\(\d{3}\)\d{3}-\d{4}"));
Besides the source for RegexFormatter, a sample program, Controller,
is provided. This demo allows you to interactively set the regular
expression used by RegexFormatter, as well as try out
some of the various options exposed by DefaultFormatter
and JFormattedTextField. The demo also includes a couple
of example regular expressions that can be viewed by changing the
selection in the JComboBox. When you press enter
in the text field, or update any of the configuration widgets, the
RegexFormatter is recreated. The valid and value
labels at the bottom of the Window update as the corresponding
properties of the JFormattedTextField change.
What about groupings?
As was previously mentioned, with regular expressions there isn't
necessarily a mapping between the String and Object.
This is only partially true. The regular expression package provides
a means to identify a region of text that matches a particular portion
of the regular expression using grouping. For example, the following
expression has two groups:
\(\d{3}\)(\d{3}-\d{4})
Group 0 is defined as the entire expression and group 1, wrapped
in parentheses and highlighted in red,
is defined as the phone number without the three-digit area code.
The string '(415)555-1212' would match the complete regular expression
as defined by group 0, and the string '555-1212' would match the
subgroup defined as group 1.
Pattern returns an instance of Matcher
that may be used to extract the text that matched a particular grouping.
RegexFormatter could expose a means to provide a mapping
between groupings and the resulting Object, as well as a mapping
from the Object to String using a similar
regular expression and set of groupings. This exercise is left for
a future article, or the ambitious reader.
|