How to Quickly Add Validation Code to Swing UIs
The Simple Validation API, available on Kenai, is a simple library for quickly adding validation code to Swing user-interfaces.
Join the DZone community and get the full member experience.
Join For FreeThe 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)
- If not, run validation on all components in the group until a Problem of Severity.FATAL is found.
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.
Opinions expressed by DZone contributors are their own.
Comments