package aliview.settings; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.text.JTextComponent; /** * Verifies user input into a {@link javax.swing.text.JTextComponent} versus a * regular expression. * *<P> This class is likely useful for a wide number of simple input needs. * See the {@link java.util.regex.Pattern} class for details regarding regular * expressions. * * <P>The {@link #main} method is provided as a developer tool for * testing regular expressions versus user input, but the principal use of this * class is to be passed to {@link javax.swing.JComponent#setInputVerifier}. * *<P> Upon detection of invalid input, this class takes the following actions : *<ul> * <li> emit a beep * <li> overwrite the <tt>JTextComponent</tt> to display the following: * INVALID: " (input data) " * <li> optionally, append the tooltip text to the content of the INVALID message ; this * is useful only if the tooltip contains helpful information regarding input. * Warning : appending the tooltip text may cause the error * text to be too long for the corresponding text field. *</ul> * *<P> The user of this class is encouraged to always place conditions on data entry * in the tooltip for the corresponding field. */ public final class RegexInputVerifier extends InputVerifier { private String defaultValue; /* * Implementation Note: * Use of JOptionPane to display error messages in an * InputVerifier seems buggy. There also seem to be issues * regarding focus and events. */ /** * Constructor. * * @param aPattern regular expression against which all user input will * be verified; <tt>aPattern.pattern</tt> satisfies * {@link Util#textHasContent}. * @param aUseToolTip indicates if the tooltip text should be appended to * error messages displayed to the user. */ RegexInputVerifier(Pattern aPattern, UseToolTip aUseToolTip){ fMatcher = aPattern.matcher(""); fUseToolTip = aUseToolTip.getValue(); } public RegexInputVerifier(Pattern aPattern, UseToolTip aUseToolTip, String defaultValue) { fMatcher = aPattern.matcher(""); fUseToolTip = aUseToolTip.getValue(); this.defaultValue = defaultValue; } /** * Enumeration compels the caller to use a style which reads clearly. */ enum UseToolTip { TRUE(true), FALSE(false); boolean getValue(){ return fToggle; } private boolean fToggle; private UseToolTip(boolean aToggle){ fToggle = aToggle; } } /** * Always returns <tt>true</tt>, in this implementation, such that focus can * always transfer to another component whenever the validation fails. * * <P>If <tt>super.shouldYieldFocus</tt> returns <tt>false</tt>, then * notify the user of an error. * * @param aComponent is a <tt>JTextComponent</tt>. */ @Override public boolean shouldYieldFocus(JComponent aComponent){ boolean isValid = super.shouldYieldFocus(aComponent); if ( isValid ){ //do nothing return true; } else { JTextComponent textComponent = (JTextComponent)aComponent; notifyUserOfError(textComponent); return false; } } /** * Return <tt>true</tt> only if the untrimmed user input matches the * regular expression provided to the constructor. * * @param aComponent must be a <tt>JTextComponent</tt>. */ public boolean verify(JComponent aComponent) { System.out.println("verify"); boolean result = false; JTextComponent textComponent = (JTextComponent)aComponent; fMatcher.reset( textComponent.getText() ); if ( fMatcher.matches() ) { result = true; } return result; } /** * The text which begins all error messages. * * The caller may examine their text fields for the presence of * <tt>ERROR_MESSAGE_START</tt>, before processing input. */ static final String ERROR_MESSAGE_START = "INVALID: "; /** * Matches user input against a regular expression. */ private Matcher fMatcher; /** * Indicates if the JTextField's tooltip text is to be appended to * error messages, as a second way of reminding the user. */ private boolean fUseToolTip; /* * Various regular expression patterns used to * construct convenience objects of this class: */ private static final String TEXT_FIELD = "^(\\S)(.){1,75}(\\S)$"; private static final String NON_NEGATIVE_INTEGER_FIELD = "(\\d){1,9}"; private static final String INTEGER_FIELD = "(-)?" + NON_NEGATIVE_INTEGER_FIELD; private static final String NON_NEGATIVE_FLOATING_POINT_FIELD = "(\\d){1,10}\\.(\\d){1,10}" ; private static final String FLOATING_POINT_FIELD = "(-)?" + NON_NEGATIVE_FLOATING_POINT_FIELD ; private static final String NON_NEGATIVE_MONEY_FIELD = "(\\d){1,15}(\\.(\\d){2})?"; private static final String MONEY_FIELD = "(-)?" + NON_NEGATIVE_MONEY_FIELD; /** * Convenience object for input of integers: ...-2,-1,0,1,2... * * <P>From 1 to 9 digits, possibly preceded by a minus sign. * Corresponds approximately to the spec of <tt>Integer.parseInt</tt>. * The limit on the number of digits is related to size of <tt>Integer.MAX_VALUE</tt> * and <tt>Integer.MIN_VALUE</tt>. */ static final RegexInputVerifier INTEGER = new RegexInputVerifier(Pattern.compile(INTEGER_FIELD), UseToolTip.FALSE) ; /** * Convenience object for input of these integers: 0,1,2... * *<P> As in {@link #INTEGER}, but with no leading minus sign. */ static final RegexInputVerifier NON_NEGATIVE_INTEGER = new RegexInputVerifier(Pattern.compile(NON_NEGATIVE_INTEGER_FIELD), UseToolTip.FALSE) ; /** * Convenience object for input of short amounts of text. * * <P>Text contains from 1 to 75 non-whitespace characters. */ static final RegexInputVerifier TEXT = new RegexInputVerifier(Pattern.compile(TEXT_FIELD), UseToolTip.FALSE) ; /** * Convenience object for input of decimals numbers, eg -23.23321, 100.25. * * <P>Possible leading minus sign, 1 to 10 digits before the decimal, and 1 to 10 * digits after the decimal. */ static final RegexInputVerifier FLOATING_POINT = new RegexInputVerifier(Pattern.compile(FLOATING_POINT_FIELD), UseToolTip.FALSE) ; /** * Convenience object for input of non-negative decimals numbers, eg 23.23321, 100.25. * * <P>As in {@link #FLOATING_POINT}, but no leading minus sign. */ static final RegexInputVerifier NON_NEGATIVE_FLOATING_POINT = new RegexInputVerifier( Pattern.compile(NON_NEGATIVE_FLOATING_POINT_FIELD), UseToolTip.FALSE ) ; /** * Convenience object for input of money values, eg -23, 100.25. * * <P>Possible leading minus sign, from 1 to 15 leading digits, and optionally * a decimal place and two decimals. */ static final RegexInputVerifier MONEY = new RegexInputVerifier(Pattern.compile(MONEY_FIELD), UseToolTip.FALSE) ; /** * Convenience object for input of non-negative money values, eg 23, 100.25. * * <P>As in {@link #MONEY}, except no leading minus sign */ static final RegexInputVerifier NON_NEGATIVE_MONEY = new RegexInputVerifier(Pattern.compile(NON_NEGATIVE_MONEY_FIELD), UseToolTip.FALSE) ; /** * If an error message is currently displayed in aComponent, then * do nothing; otherwise, display an error message to the user in a * aComponent (see class description for format of message). */ private void notifyUserOfError(JTextComponent aTextComponent){ if ( isShowingErrorMessage(aTextComponent) ){ //do nothing, since user has not yet re-input. } else { showErrorMessage(aTextComponent); } } private boolean isShowingErrorMessage(JTextComponent aTextComponent){ return aTextComponent.getText().startsWith(ERROR_MESSAGE_START); } private void showErrorMessage(JTextComponent aTextComponent) { StringBuilder message = new StringBuilder(ERROR_MESSAGE_START); message.append("\""); message.append(aTextComponent.getText()); message.append("\""); if ( fUseToolTip ) { message.append(aTextComponent.getToolTipText()); } aTextComponent.setText(message.toString()); } }