Tim Boudreau is a noted technology consultant, evangelist and author. While perhaps most broadly known for his leadership on Sun Microsystems’ NetBeans, those who’ve worked with Tim remark most on his technical chops, passion for a great challenge and rare gift of great communication. And, as a former troubadour, he’s pretty tough when it comes to bad 70s rock lyrics too. A real renaissance programmer. Tim has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

How to Quickly Add Validation Code to Swing UIs

08.03.2009
| 44993 views |
  • submit to reddit

The Simple Validation API, available on Kenai, is a simple library for quickly adding validation code to Swing user-interfaces. It handles validating user input when the user changes a component's value, showing error messages and decorating components to indicate which component is the source of the problem. It contains a large number of built-in validators to handle most common situations, such as validating numbers, email addresses, urls and so forth.

The primary goal is to make it easy to retrofit validation code on existing UIs without needing to rewrite anything or add more than a few lines of code.

Very often, data a user enters needs to be validated - checked for legality. The user interface (UI) needs to make sure that what the user typed is legal; if it is not, the user should be shown what the problem is, and possibly some parts of the UI should become disabled.

For example, when a dialog is shown asking the user to type something, if there is a problem with what they typed, the dialog's OK button should be disabled and the user should be shown a message indicating what the problem is.

The library consists of a few simple concepts:

  • Validators - an interface for things that validate objects of some type, usually String. A validator produces

  • Problems - information to show the user. Problems have three severities, FATAL, WARNING and INFO.

  • ValidationGroup - A group of components which are presented together can affect each other. Typically there is one ValidationGroup per panel. A change in one component in the UI may trigger revalidation of the content of other components.

  • ValidationUI - A user interface which can display a problem to a user. You can either use the built-in ValidationPanel for this, or implement it over an existing UI by adding two methods. ValidationGroup.createProblemLabel() can create a label which shows problems with the appropriate color and icons.

  • Converter - a Validator which does no validation of its own, but takes the object type the UI produces (e.g. javax.swing.text.Document), converts it to a type better suited for validation (e.g. java.lang.String) and invokes another validator against the converted object

  • Validators - an enum of commonly needed, built-in validators which should handle most common UI validation situations

Validators can be chained together in order of precedence - there will be one Validator per component, but that validator may actually chain together several. This is done quite simply, e.g.

Validator<String> v = Validators.merge(validator1, validator2, validator3)

The library contains pre-built validators for most common cases in user interfaces, so that most UIs will not need any custom validation code of there own. For example, to validate a URL, you would simply call

validationGroup.add (theTextField, 
Validators.REQUIRE_NON_EMPTY_STRING,
Validators.NO_WHITESPACE,
Validators.VALID_URL);
If you did want to include a your own custom validator, it would be as simple as
validationGroup.add (theTextField, 
Validators.REQUIRE_NON_EMPTY_STRING,
Validators.NO_WHITESPACE,
new MyCustomValidator());

Downloading the Simple Validation Library

JAR file, javadoc, source code and a NetBeans library module are available in the downloads area

Screen Shots

The following is from the NetBeans Java Card modules, showing WARNINGs and ERRORs. Very little code was needed to add validation to this UI.

What This Library Contains

SimpleValidation is designed with the following goals in mind:

  • Minimal conceptual overhead
  • Ease of adoption
    • Often validation can be added to an existing UI with only a few lines of code
  • Provide useful pre-built validators for common cases in Swing user interfaces, so most UIs do not need to include hand-written validation code, such as
    • Non empty string
    • Non negative number
    • Java identifier (i.e. not a keyword)
    • Numbers
    • Integers
    • Hexadecimal
    • URLs (including validating illegal charsets and out-of-spec server ids and port numbers, which java.net.URL does not do)
    • Legal Filenames
    • No whitespace in String
    • Filename must represent existing file
    • Filename must represent non-existent file
    • File which is directory
    • File which is not a directory
    • Internet host name
    • Internet email address
    • String encoding name
    • Number range
    • Minimum/maximum input length
    • Regular expression matching
    • Locale-specific number validation
    • IP addresses
    • String encodable in a particular character encoding
    • Character encoding names
    • Java package name
    • String may not start with digit
    • String matches a particular regular expression

Additionally there are built in validators to split a string into components and then run some other validator on each component, and validators that wrap an input validator and first call String.trim() on the input string.

What This Library Is Not

This library is not a general purpose validation framework for anything and everything (for example, ad-hoc Java Beans, etc.), nor is it intended to encapsulate a theoretically complete representation of validation or string->ad-hoc-object->string conversion. Such libraries already exist.

Therefore, while it could contain concepts such as a ProblemSource which extends a MessageSource and so forth, it does not; severities could be conceived as floats from 0.0F to 1.0F; instead, there are three severity levels, which are adequate to solve most common cases. One of the design goals is to minimize conceptual surface-area - i.e. any abstraction that is does not have immediate utility should not be something people see in the list of classes in the javadoc.

This library is intended to make certain common cases in user interfaces (such as not allowing the user to proceed if a field is blank, or, say, contains an invalid URL) easy, not save the world.

A Simple Example

Below is all the code necessary to validate a URL in a dialog

  public static void main(String[] args) {
//This is our actual UI
JPanel inner = new JPanel();
JLabel lbl = new JLabel("Enter a URL");
JTextField f = new JTextField();
f.setColumns(40);
//Setting the component name is important - it is used in
//error messages
f.setName("URL");
inner.add(lbl);
inner.add(f);
//Create a ValidationPanel - this is a panel that will show
//any problem with the input at the bottom with an icon
ValidationPanel panel = new ValidationPanel();
panel.setInnerComponent(inner);
ValidationGroup group = panel.getValidationGroup();
//This is all we do to validate the URL:
group.add(f, Validators.REQUIRE_NON_EMPTY_STRING,
Validators.NO_WHITESPACE,
Validators.URL_MUST_BE_VALID);
//Convenience method to show a simple dialog
if (panel.showOkCancelDialog("URL")) {
System.out.println("User clicked OK. URL is " + f.getText());
System.exit(0);
} else {
System.err.println("User clicked cancel.");
System.exit(1);
}
}

Contributing Validators

We welcome contributions of new Validators to handle additional common cases. There are three simple rules for contributions:

  • Contributed validators should not require this library to depend on classes other than those in the JDK
    • For very commonly used libraries that add multiple types, they can be hosted here but should live in their own JAR file
  • Generally, don't expose the implementation type of your Validator; rather, add a constant or factory method to org.netbeans.validation.api.builtin.Validators. Stateless validators should use an enum constant; stateful validators (ones which are passed some parameter when they are created) should get a factory method.
  • Contributed validators should have unit tests

Other Common Questions

Why is the package name org.netbeans.*?

This library was created to add validation to UI components in NetBeans which did not have any and needed it. It was useful and general enough to make it into a separate project.

I added a (number, url, etc.) validator, but it says an empty string is fine. What's wrong?

Each validator does one thing only. They can be chained together using Validators.merge(), and are called in order until one turns up a fatal problem. Anything that excludes an empty string should have Validators.REQUIRE_NON_EMPTY_STRING as the first validator in the chain. A validator generally validates some input; more meaningful user-feedback can be achieved by chaining them together - "URL may not be empty" is a nicer message than " ' ' is not a valid URL". Validators expecting some input are allowed to be silent when there is none - that's what Validators.REQUIRE_NON_EMPTY_STRING is for.

What is NbBundle (or Utilities, Exceptions, etc.)?

NbBundle is the class which NetBeans uses internally to handle localized strings. It does a better job of caching and memory management than any of the stock JDK ways of handling resource bundles and handles decoding embedded formatted strings.

The nbstubs/ subproject contains working stub versions of all such classes; they are automatically bundled in the distribution JAR. You don't need any parts of NetBeans to use this library; since the library was designed originally to work inside NetBeans, it uses them internally.

How do I use this library from a NetBeans module?

Currently, you need to build it (without calling the release Ant target, so it will link against the real org.openide.util classes, not the stub versions), and create a library wrapper module for it.

What about I18N - component names are not usually localized!

There are two ways to solve this:

  • Set the component name to a localized string
  • Use putClientProperty(ValidationListener.CLIENT_PROP_NAME, theLocalizedName) instead, if the component name is already being used for some other purpose

What is the NetBeans module download

It is a NetBeans plugin which adds the SimpleValidation library to the list of built-in libraries you can use from your project, and includes the javadoc and the source code for debugging.

UML Diagrams

Some UML diagrams are available on the UML page

Brainstorming

This project is nowhere near a 1.0 release - add your ideas to the brainstorming page

Advanced Features

A few features are worth mentioning here:

Chaining Validation Groups

ValidationGroup has methods addValidationGroup() and RemoveValidationGroup. These are useful when you are composing a panel that has its own ValidationGroup inside another one which may also have some UI for validation. For example, a master-detail view in a dialog with a list of items, each of which has a customizer would probably have a ValidationGroup for anything that can be entered in the parent form; the customizer would have its own. Simply add the child component's ValidationGroup to the parent's validation group. You have the option of turning off the UI of the child (if, say, that would cause your UI to have two error labels showing the same thing).

Multiple ValidationUIs

You can add multiple validation uis to a validation group, simply by calling addUI(). This is useful, for example, if you have some base code that shows all OK/cancel dialogs in your application - if it implements ValidationUI, you can control the enabled state of the OK button. But you also probably want to show feedback (ValidationGroup.createProblemLabel() is useful for this).

Making programmatic modifications to validated components

Sometimes you may want to set the values in a number of component programmatically. For example, in the screen shots above, whenever the Http Port value is changed, the URLs at the bottom should be updated if they were not manually edited already.

When you are making programmatic modifications, you don't want that to trigger a storm of validation - your code knows what it's doing. So just wrap any code like that in a Runnable and pass it to ValidationGroup.modifyComponents(). This will disable all validation (note that modifyComponents() is reentrant, so your runnable can trigger another call to modifyComponents() without a problem - validation will resume when the last runnable exists).

Threading

All methods of ValidationGroup must be called on the AWT event thread. This is enforced with assertions. It is never, ever safe to modify or even construct a Swing component on any thread except the event thread. The exceptions to this rule in standard Swing can be counted on one hand. All UI work needs to happen on the event thread, period.

javax.swing.text.Document is thread safe for modifications. If a ValidationListener receives a DocumentEvent on another thread, it will schedule validation to run on the event thread using EventQueue.invokeLater(). Validation always happens on the event thread.

Lifecycle and Memory Management

A ValidationGroup holds references to the UI components added to it. The best way to avoid memory leaks is to make sure that there is one ValidationGroup per panel, and that it is not referenced by anything but that panel. That way the validation group will only exist as long as the panel does and can be garbage collected when the panel and its components are.

In the case that validation groups are composed together, make sure to remove any child validation group from its parent if the child's associated UI is no longer in use.

Performance

Validation can run at any time - in particular, because validators come in groups associated with multiple components, your validator may be run because of a change in a completely different component. The algorithm for validation works like this:

  • When a component changes (according to the ValidationStrategy you chose), its Validator(s) run.
  • If that component's validator produces a Problem of Severity.FATAL, stop and update the UI with that error
    • If not, run validation on all components in the group until a Problem of Severity.FATAL is found.
      • If no problem, clear the problem in the UI
      • If a non-fatal problem, update the UI with the earliest Problem with the highest severity (FATAL, WARNING, INFO)

So, to put it succinctly, validation should be fast - if validating requires a network connection or heavy-duty file I/O, this library is the wrong solution.

There are some ideas on the brainstorming page for adding support for asynchronous validation in the future.

From http://kenai.com/projects/simplevalidation/pages/Home

Published at DZone with permission of its author, Tim Boudreau.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Jacek Furmankiewicz replied on Mon, 2009/08/03 - 9:15am

Very interesting. I will look in the future at potentially integrating it into the Swing JavaBuilder

http://javabuilders.org

 to add as the default handler for our UI validation logic.

Hantsy Bai replied on Tue, 2009/08/04 - 2:30am

This validator use UI Component directly.

Why not intergrat Bean Validation standard into NetBeans platform , and apply validation on DataModel , and set  validation trigger rule on UI component ,  when validation fails, notify the UI  component to display error message.

Tim Boudreau replied on Wed, 2009/08/05 - 12:22am

Why not intergrat Bean Validation standard into NetBeans platform , and apply validation on DataModel...

I'm aware of the Bean Validation project (although its status seems nebulous).  The important thing here was ease of integration into existing user interface code.  I created this for selfish reasons - I inherited a bunch of customizer code that does no validation whatsoever (try entering garbage into a mobility project's file encoding field - it will accept it without complaint and then throw exceptions).  Also, data models for Swing components don't always follow the bean model terribly clearly.  And nobody likes writing code like

String s = jTextField1.getText().trim();
if  (s.length() == 0) {
error ("...");
return;
try {
   Integer.parseInt (s);
} catch (NumberFormatException nfe) {
error ("...");
return;
}
if (s.contains(...
and NetBeans and many other Swing codebases are littered with this stuff.

Anything that sets out to solve all the world's validation problems is going to be too general (and large - both in size and conceptual surface area) for the problem I'm trying to solve, which is to allow you to take an existing piece of UI code, and retrofit validation and good error reporting with a minimum of code and a learning curve near zero.

I looked at (and link to) the JGoodies Validation stuff. It looks very good - if you're creating a UI from scratch or have the time and luxury to rewrite it to have a data model for the entire UI's contents and so forth. But if you're bug-fixing existing UI code, and have plenty of other tasks on your list, a framework designed for UIs you are writing from scratch is not going to help.

So my hope is that this project fills the niche for the programmer who has some UI code, needs good error reporting and validation, and wants a solution that lets them add this to a fairly complex panel in under an hour.

I see it complementing, not competing, with the existing validation solutions out there - in fact, for those that provide their own validators, it would be relatively easy to write an adapter Validator subclass from this framework that works with those others. Or the other way around.

-Tim

Javier Ortiz replied on Fri, 2010/02/19 - 1:50pm

Is there a way to validate stuff witout the validation pane?m I have a Top component already in place with the fields in it so I don't need another pane. I just need the functionality.

Gustavo Sun replied on Thu, 2010/06/24 - 6:43am

Hi tim i liked so much about your api, but getting problems, have you a complete tutorial about it? because i have jframe , and when i use panel.setInnerComponent(jpanel); in the construtor of the jframe it get problems. Can you help me?

Valerio Galano replied on Thu, 2013/09/19 - 3:49am

Hi,
I'm trying to use Simple Validation with your example, but, my Netbeans gives error "package Validators do not exists".

I downloaded class from download page and imported into my project. Everithing is ok, but not Validators class.

Can you help, please?

Valerio Galano replied on Mon, 2013/09/23 - 10:04am in response to: Valerio Galano

 Resolved: It's StringValidators, not Validators as shown in your code.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.