OpenOak Technologies
JTextField Validation - PositiveIntegerOnly.java
Overview
When developing Java applications, you want to give the user the best experience possible while making sure their data entry works with your program. One way to do this is to validate the user's input as they're entering it. It's also helpful to give feedback to the user if their input does not meet the validation requirement.
NOTE: This is the first part of a three-part article. Go back to article introduction.

We'll start with Plain and extend it with our validation routines. To do this, we'll add another FocusListener (which takes the place of Plain's FocusListener, but when we run the clearMessage(), Plain checks to see if entry is required and acts accordingly). We also add as an InputVerifier and a text field Document.
We'll look at each one in detail as we go through the code.

/*
 * Copyright(c) 2008 OpenOak Technologies 
 * Licensed under the OpenOak Learning License
 */

Declare the package as part of a larger package and do the imports.

package com.openoak.textfield;

import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

PositiveIntegerOnly takes Plain and extends it to allow for the validation routines. It also implements FocusListener to check when the user leaves the field, which we will discuss below.

/**
 *  Creates an Positive integer-only text field
 */
public class PositiveIntegerOnly extends Plain implements FocusListener {

Here are the private variables that we store:
hasRange -- if we set a range for our integer, then this will be true.
minimumValue -- The Minimum value we want an entry to be (optional).
maximumValue -- The Maximum value we want an entry to be (optional).
rangeMessage -- The message to display indicating the range.

    private boolean hasRange = false;
    private int minimumValue = 0;
    private int maximumValue = 0;
    private String rangeMessage = "";

The constructor adds the Document which limits the text entry to integers, adds the FocusListener, adds the InputVerifier which checks to make sure we're entering proper integers, and sets the text to Right-justified.

    public PositiveIntegerOnly() {
        this.setDocument(new IntegerDocument());
        this.setHorizontalAlignment(PositiveIntegerOnly.RIGHT);
        this.addFocusListener(this);
        this.setInputVerifier(new RangeVerifier());
    }

Sets the range of integers we want the user to be able to enter (optional). If we don't do this, it will accept any positive integer.

    /**
     * Sets the input range for the integer field.  The default is any Positive Integer
     * 
     * @param minimumValue The minimum value to set
     * @param maximumValue The maximum value to set
     */
    public void setRange(int minimumValue, int maximumValue) {
        if ((minimumValue < maximumValue) && (minimumValue >= 0)) {
            hasRange = true;
            this.minimumValue = minimumValue;
            this.maximumValue = maximumValue;
            rangeMessage = "[" + minimumValue + "::" + maximumValue + "]";
        }
        
    }

This function returns the value of the entered text as a positive integer instead of making the programer parse it out each time they use this text field.

    /**
     * Gets the value of the field as a positive integer. 
     * 

NOTE: If the field is blank, it will return a zero. The program should * be prepared for this. If you need to check for null values, you can call isNull(). * * @return the value of the field as an integer */ public int getValue(){ String theValue = this.getText(); int value; if (theValue.length() < 1) { value = 0; } else { try { value = Integer.parseInt(theValue); } catch (java.lang.NumberFormatException exp) { value = 0; } } return value; }

This function sets the entry from an integer to keep the programmer from needing to do something special each time. Since we're dealing with integers, this should make it easier.

    /**
     * Sets the value of the text field, provided that a positive integer is sent
     * 
     * @param newValue The value to set
     */
    public void setValue (int newValue) {
        if (newValue >= 0) {
            super.setText(newValue + "");
        }
    }

Overrides the default setText() function to check to make sure we're passing something that could be converted to an integer.

    /**
     * Sets the value of the text field.  This will check to see if it's an integer
     * first.  If it's passed something that it can identify as an integer, it will
     * blank out the text field.
     * 
     * @param newText The value you wish to set
     */
    @Override
    public void setText(String newText){
        if (newText.length() > 0){
            try {
                int newInteger = Integer.parseInt(newText);
                super.setText(newInteger + "");
            } catch (java.lang.NumberFormatException exp){
                super.setText("");
            }
        } else {
            super.setText("");
        }
    }

These functions are there to call the show Message routines from Plain. We need to do this so they will be available in the subclasses below.

    private void showMyMessage(String message) {
        this.showMessage(message);
    }
    
    private void showMyMessageWarning(String message) {
        this.showMessageWarning(message);
    }
    
    private void showMyMessageError(String message) {
        this.showMessageError(message);
    }
    
    private void hideMyMessage(){
        this.clearMessage();
    }

The first subclass is the Document class that extends PlainDocument. This is to override the default "Document" of the textfield so that we can only type integers. It does two things: check when we insert and when we delete against a validation rule.

    /**
     * The 'document' that controls the text input
     */
    class IntegerDocument extends PlainDocument {
        
        private int currentValue = 0;
        
        public IntegerDocument(){
            
        }

This runs whenever we insert a string into the TextField or type anyting into it. The main thing it does for validation is to run checkInput (described below) against the proposed value.

        @Override
        public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException {
            if (string == null) {
                return;
            } else {
                String newValue;
                
                int length = getLength();
                if (length == 0) {
                    newValue = string;
                } else {
                    String currentContent = getText(0, length);
                    StringBuffer currentBuffer = new StringBuffer(currentContent);
                    currentBuffer.insert(offset, string);
                    newValue = currentBuffer.toString();
                }
                currentValue = checkInput(newValue, offset);
                super.insertString(offset, string, attributes);
            }
        }

This runs when we delete text from the TextField and also runs checkInput against the proposed value.

        @Override
        public void remove(int offset, int length) throws BadLocationException {
            int currentLength = getLength();
            String currentContent = getText(0, currentLength);
            String before = currentContent.substring(0, offset);
            String after = currentContent.substring(length + offset, currentLength);
            String newValue = before + after;
            currentValue = checkInput(newValue, offset);
            super.remove(offset, length);
        }

checkInput is the heart of the Document. It gets the proposed value and runs it against validation to check to see if we have something that can be parsed as an integer. We check the String, character by character, to see if it's a number. Here we use a Pattern Matcher on each character to see if it's from zero to nine. We can use this technique to do a variety of matching.
If the character isn't from zero to nine, we throw an error and let the user know that they should only be entering an integer. The last thing we do is to run the Integer parser to make sure what was entered can be translated as an integer.

        public int checkInput(String proposedValue, int offset) throws BadLocationException {
            int proposedLength = proposedValue.length();
            
            if (proposedLength > 0) {
                boolean isOK = false;
                String regEx = "[0-9]";
                for (int i = 0; i < proposedLength; i++){
                    char checkChar = proposedValue.charAt(i);

                    Pattern maskPattern = Pattern.compile(regEx);
                    String maskMatch = String.valueOf(checkChar);
                    Matcher matcher = maskPattern.matcher(maskMatch);
                    if (matcher.find()) {
                        isOK = true;
                    } else {
                        isOK = false;
                        if (hasRange) {
                            showMyMessageWarning(rangeMessage + " Integer only");
                        } else {
                            showMyMessageWarning("Positive Integer only");
                        }
                        throw new BadLocationException(proposedValue, offset);
                    }
                    
                }
                if (isOK) {
                    try {
                        int tryValue = Integer.parseInt(proposedValue);
                    } catch (java.lang.NumberFormatException exp) {
                        isOK = false;
                        showMyMessageError ("Integer out of range.");
                    }
                }
                if (isOK) {
                    if (!hasRange) {
                        hideMyMessage();
                    } else {
                        if (verifyInRange(proposedValue)) {
                            showMyMessage(rangeMessage);
                        } else {
                            showMyMessageWarning(rangeMessage);
                        }
                    }
                }
            } else {
                return 0;
            }
            return 0;
        }
    }

When the user enters the field, we show the range that the programmer has set for the integer values.

    public void focusGained(FocusEvent e) {
        if (hasRange) {
            showMyMessage(rangeMessage);
        }
    }

When the user leaves the TextField, we run the Integer parser one last time to make sure we're OK with the integer value. This also helps with the display by changing an entry like "0000010" to "10".

    public void focusLost(FocusEvent e) {
        try {
            int newValue = Integer.parseInt(this.getText());
            super.setText(newValue + "");
        } catch (java.lang.NumberFormatException exp){
            super.setText("");
        }
        hideMyMessage();
    }

The second subclass is the InputVerifier which checks what the user has entered is indeed a parsable integer and is within the range we may have set. If we don't pass these tests, the TextField won't let the user leave.

    public class RangeVerifier extends InputVerifier{

We override the verify function to run the verifyInRange() function below.

        @Override
        public boolean verify(JComponent input) {
            JTextField tf = (JTextField)input;
            String myStringValue = tf.getText();
            return verifyInRange(myStringValue);
        }
        
    }

This function checks to see if the programmer has set the range. If so, then we parse the text into an integer and check it against the range. If not, then we simply try to parse the integer. If either of these fail or the integer is out of the set range, we tell the TextField to keep the user until they fix it and let the user know what they need to do.

    private boolean verifyInRange(String value) {
        if (value.length() > 0) {
            
             if (hasRange) {
                try {
                    int myValue = Integer.parseInt(value);
                    if ((myValue >= minimumValue) && (myValue <= maximumValue)) {
                        return true;
                    } else {
                        showMyMessageError(rangeMessage + " Integer Range");
                        return false;
                    }
                } catch (java.lang.NumberFormatException exp) {
                    showMyMessageError(rangeMessage + " Integer Range");
                    return false;
                }
            } else {
                    try{
                        int myValue = Integer.parseInt(value);
                    } catch (java.lang.NumberFormatException exp) {
                        showMyMessageError("Integer out of range.");
                        return false;
                    }
                }
            return true;
            
        }
        return true;
    }
}

This concludes the TextField validation. We can take these techniques to use for other types of validation by building on the Plain class.