content: 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());
  	}
  }
  