| /* MaskFormatter.java -- |
| Copyright (C) 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.swing.text; |
| |
| import gnu.java.lang.CPStringBuilder; |
| |
| import java.text.ParseException; |
| |
| import javax.swing.JFormattedTextField; |
| |
| /** |
| * @author Anthony Balkissoon abalkiss at redhat dot com |
| * |
| */ |
| public class MaskFormatter extends DefaultFormatter |
| { |
| // The declaration of the valid mask characters |
| private static final char NUM_CHAR = '#'; |
| private static final char ESCAPE_CHAR = '\''; |
| private static final char UPPERCASE_CHAR = 'U'; |
| private static final char LOWERCASE_CHAR = 'L'; |
| private static final char ALPHANUM_CHAR = 'A'; |
| private static final char LETTER_CHAR = '?'; |
| private static final char ANYTHING_CHAR = '*'; |
| private static final char HEX_CHAR = 'H'; |
| |
| /** The mask for this MaskFormatter **/ |
| private String mask; |
| |
| /** |
| * A String made up of the characters that are not valid for input for |
| * this MaskFormatter. |
| */ |
| private String invalidChars; |
| |
| /** |
| * A String made up of the characters that are valid for input for |
| * this MaskFormatter. |
| */ |
| private String validChars; |
| |
| /** A String used in place of missing chracters if the value does not |
| * completely fill in the spaces in the mask. |
| */ |
| private String placeHolder; |
| |
| /** A character used in place of missing characters if the value does |
| * not completely fill in the spaces in the mask. |
| */ |
| private char placeHolderChar = ' '; |
| |
| /** |
| * Whether or not stringToValue should return literal characters in the mask. |
| */ |
| private boolean valueContainsLiteralCharacters = true; |
| |
| /** A String used for easy access to valid HEX characters **/ |
| private static String hexString = "0123456789abcdefABCDEF"; |
| |
| /** An int to hold the length of the mask, accounting for escaped characters **/ |
| int maskLength = 0; |
| |
| public MaskFormatter () |
| { |
| // Override super's default behaviour, in MaskFormatter the default |
| // is not to allow invalid values |
| setAllowsInvalid(false); |
| } |
| |
| /** |
| * Creates a MaskFormatter with the specified mask. |
| * @specnote doesn't actually throw a ParseException although it |
| * is declared to do so |
| * @param mask |
| * @throws java.text.ParseException |
| */ |
| public MaskFormatter (String mask) throws java.text.ParseException |
| { |
| this(); |
| setMask (mask); |
| } |
| |
| /** |
| * Returns the mask used in this MaskFormatter. |
| * @return the mask used in this MaskFormatter. |
| */ |
| public String getMask() |
| { |
| return mask; |
| } |
| |
| /** |
| * Returns a String containing the characters that are not valid for input |
| * for this MaskFormatter. |
| * @return a String containing the invalid characters. |
| */ |
| public String getInvalidCharacters() |
| { |
| return invalidChars; |
| } |
| |
| /** |
| * Sets characters that are not valid for input. If |
| * <code>invalidCharacters</code> is non-null then no characters contained |
| * in it will be allowed to be input. |
| * |
| * @param invalidCharacters the String specifying invalid characters. |
| */ |
| public void setInvalidCharacters (String invalidCharacters) |
| { |
| this.invalidChars = invalidCharacters; |
| } |
| |
| /** |
| * Returns a String containing the characters that are valid for input |
| * for this MaskFormatter. |
| * @return a String containing the valid characters. |
| */ |
| public String getValidCharacters() |
| { |
| return validChars; |
| } |
| |
| /** |
| * Sets characters that are valid for input. If |
| * <code>validCharacters</code> is non-null then no characters that are |
| * not contained in it will be allowed to be input. |
| * |
| * @param validCharacters the String specifying valid characters. |
| */ |
| public void setValidCharacters (String validCharacters) |
| { |
| this.validChars = validCharacters; |
| } |
| |
| /** |
| * Returns the place holder String that is used in place of missing |
| * characters when the value doesn't completely fill in the spaces |
| * in the mask. |
| * @return the place holder String. |
| */ |
| public String getPlaceholder() |
| { |
| return placeHolder; |
| } |
| |
| /** |
| * Sets the string to use if the value does not completely fill in the mask. |
| * If this is null, the place holder character will be used instead. |
| * @param placeholder the String to use if the value doesn't completely |
| * fill in the mask. |
| */ |
| public void setPlaceholder (String placeholder) |
| { |
| this.placeHolder = placeholder; |
| } |
| |
| /** |
| * Returns the character used in place of missing characters when the |
| * value doesn't completely fill the mask. |
| * @return the place holder character |
| */ |
| public char getPlaceholderCharacter() |
| { |
| return placeHolderChar; |
| } |
| |
| /** |
| * Sets the char to use if the value does not completely fill in the mask. |
| * This is only used if the place holder String has not been set or does |
| * not completely fill in the mask. |
| * @param placeholder the char to use if the value doesn't completely |
| * fill in the mask. |
| */ |
| public void setPlaceholderCharacter (char placeholder) |
| { |
| this.placeHolderChar = placeholder; |
| } |
| |
| /** |
| * Returns true if stringToValue should return the literal |
| * characters in the mask. |
| * @return true if stringToValue should return the literal |
| * characters in the mask |
| */ |
| public boolean getValueContainsLiteralCharacters() |
| { |
| return valueContainsLiteralCharacters; |
| } |
| |
| /** |
| * Determines whether stringToValue will return literal characters or not. |
| * @param containsLiteralChars if true, stringToValue will return the |
| * literal characters in the mask, otherwise it will not. |
| */ |
| public void setValueContainsLiteralCharacters (boolean containsLiteralChars) |
| { |
| this.valueContainsLiteralCharacters = containsLiteralChars; |
| } |
| |
| /** |
| * Sets the mask for this MaskFormatter. |
| * @specnote doesn't actually throw a ParseException even though it is |
| * declared to do so |
| * @param mask the new mask for this MaskFormatter |
| * @throws ParseException if <code>mask</code> is not valid. |
| */ |
| public void setMask (String mask) throws ParseException |
| { |
| this.mask = mask; |
| |
| // Update the cached maskLength. |
| int end = mask.length() - 1; |
| maskLength = 0; |
| for (int i = 0; i <= end; i++) |
| { |
| // Handle escape characters properly - they don't add to the maskLength |
| // but 2 escape characters in a row is really one escape character and |
| // one literal single quote, so that does add 1 to the maskLength. |
| if (mask.charAt(i) == '\'') |
| { |
| // Escape characters at the end of the mask don't do anything. |
| if (i != end) |
| maskLength++; |
| i++; |
| } |
| else |
| maskLength++; |
| } |
| } |
| |
| /** |
| * Installs this MaskFormatter on the JFormattedTextField. |
| * Invokes valueToString to convert the current value from the |
| * JFormattedTextField to a String, then installs the Actions from |
| * getActions, the DocumentFilter from getDocumentFilter, and the |
| * NavigationFilter from getNavigationFilter. |
| * |
| * If valueToString throws a ParseException, this method sets the text |
| * to an empty String and marks the JFormattedTextField as invalid. |
| */ |
| public void install (JFormattedTextField ftf) |
| { |
| super.install(ftf); |
| if (ftf != null) |
| { |
| try |
| { |
| valueToString(ftf.getValue()); |
| } |
| catch (ParseException pe) |
| { |
| // Set the text to an empty String and mark the JFormattedTextField |
| // as invalid. |
| ftf.setText(""); |
| setEditValid(false); |
| } |
| } |
| } |
| |
| /** |
| * Parses the text using the mask, valid characters, and invalid characters |
| * to determine the appropriate Object to return. This strips the literal |
| * characters if necessary and invokes super.stringToValue. If the paramter |
| * is invalid for the current mask and valid/invalid character sets this |
| * method will throw a ParseException. |
| * |
| * @param value the String to parse |
| * @throws ParseException if value doesn't match the mask and valid/invalid |
| * character sets |
| */ |
| public Object stringToValue (String value) throws ParseException |
| { |
| return super.stringToValue(convertStringToValue(value)); |
| } |
| |
| private String convertStringToValue(String value) |
| throws ParseException |
| { |
| CPStringBuilder result = new CPStringBuilder(); |
| char valueChar; |
| boolean isPlaceHolder; |
| |
| int length = mask.length(); |
| for (int i = 0, j = 0; j < length; j++) |
| { |
| char maskChar = mask.charAt(j); |
| |
| if (i < value.length()) |
| { |
| isPlaceHolder = false; |
| valueChar = value.charAt(i); |
| if (maskChar != ESCAPE_CHAR && maskChar != valueChar) |
| { |
| if (invalidChars != null |
| && invalidChars.indexOf(valueChar) != -1) |
| throw new ParseException("Invalid character: " + valueChar, i); |
| if (validChars != null |
| && validChars.indexOf(valueChar) == -1) |
| throw new ParseException("Invalid character: " + valueChar, i); |
| } |
| } |
| else if (placeHolder != null && i < placeHolder.length()) |
| { |
| isPlaceHolder = true; |
| valueChar = placeHolder.charAt(i); |
| } |
| else |
| { |
| isPlaceHolder = true; |
| valueChar = placeHolderChar; |
| } |
| |
| // This switch block on the mask character checks that the character |
| // within <code>value</code> at that point is valid according to the |
| // mask and also converts to upper/lowercase as needed. |
| switch (maskChar) |
| { |
| case NUM_CHAR: |
| if (! Character.isDigit(valueChar)) |
| throw new ParseException("Number expected: " + valueChar, i); |
| result.append(valueChar); |
| i++; |
| break; |
| case UPPERCASE_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(Character.toUpperCase(valueChar)); |
| i++; |
| break; |
| case LOWERCASE_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(Character.toLowerCase(valueChar)); |
| i++; |
| break; |
| case ALPHANUM_CHAR: |
| if (! Character.isLetterOrDigit(valueChar)) |
| throw new ParseException("Letter or number expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case LETTER_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case HEX_CHAR: |
| if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) |
| throw new ParseException("Hexadecimal character expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case ANYTHING_CHAR: |
| result.append(valueChar); |
| i++; |
| break; |
| case ESCAPE_CHAR: |
| // Escape character, check the next character to make sure that |
| // the literals match |
| j++; |
| if (j < length) |
| { |
| maskChar = mask.charAt(j); |
| if (! isPlaceHolder && getValueContainsLiteralCharacters() |
| && valueChar != maskChar) |
| throw new ParseException ("Invalid character: "+ valueChar, i); |
| if (getValueContainsLiteralCharacters()) |
| { |
| result.append(maskChar); |
| } |
| i++; |
| } |
| else if (! isPlaceHolder) |
| throw new ParseException("Bad match at trailing escape: ", i); |
| break; |
| default: |
| if (! isPlaceHolder && getValueContainsLiteralCharacters() |
| && valueChar != maskChar) |
| throw new ParseException ("Invalid character: "+ valueChar, i); |
| if (getValueContainsLiteralCharacters()) |
| { |
| result.append(maskChar); |
| } |
| i++; |
| } |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * Returns a String representation of the Object value based on the mask. |
| * |
| * @param value the value to convert |
| * @throws ParseException if value is invalid for this mask and valid/invalid |
| * character sets |
| */ |
| public String valueToString(Object value) throws ParseException |
| { |
| String string = value != null ? value.toString() : ""; |
| return convertValueToString(string); |
| } |
| |
| /** |
| * This method takes in a String and runs it through the mask to make |
| * sure that it is valid. If <code>convert</code> is true, it also |
| * converts letters to upper/lowercase as required by the mask. |
| * @param value the String to convert |
| * @return the converted String |
| * @throws ParseException if the given String isn't valid for the mask |
| */ |
| private String convertValueToString(String value) |
| throws ParseException |
| { |
| CPStringBuilder result = new CPStringBuilder(); |
| char valueChar; |
| boolean isPlaceHolder; |
| |
| int length = mask.length(); |
| for (int i = 0, j = 0; j < length; j++) |
| { |
| char maskChar = mask.charAt(j); |
| if (i < value.length()) |
| { |
| isPlaceHolder = false; |
| valueChar = value.charAt(i); |
| if (maskChar != ESCAPE_CHAR && valueChar != maskChar) |
| { |
| if (invalidChars != null |
| && invalidChars.indexOf(valueChar) != -1) |
| throw new ParseException("Invalid character: " + valueChar, |
| i); |
| if (validChars != null && validChars.indexOf(valueChar) == -1) |
| throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar, |
| i); |
| } |
| } |
| else if (placeHolder != null && i < placeHolder.length()) |
| { |
| isPlaceHolder = true; |
| valueChar = placeHolder.charAt(i); |
| } |
| else |
| { |
| isPlaceHolder = true; |
| valueChar = placeHolderChar; |
| } |
| |
| // This switch block on the mask character checks that the character |
| // within <code>value</code> at that point is valid according to the |
| // mask and also converts to upper/lowercase as needed. |
| switch (maskChar) |
| { |
| case NUM_CHAR: |
| if ( ! isPlaceHolder && ! Character.isDigit(valueChar)) |
| throw new ParseException("Number expected: " + valueChar, i); |
| result.append(valueChar); |
| i++; |
| break; |
| case UPPERCASE_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(Character.toUpperCase(valueChar)); |
| i++; |
| break; |
| case LOWERCASE_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(Character.toLowerCase(valueChar)); |
| i++; |
| break; |
| case ALPHANUM_CHAR: |
| if (! Character.isLetterOrDigit(valueChar)) |
| throw new ParseException("Letter or number expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case LETTER_CHAR: |
| if (! Character.isLetter(valueChar)) |
| throw new ParseException("Letter expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case HEX_CHAR: |
| if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) |
| throw new ParseException("Hexadecimal character expected", i); |
| result.append(valueChar); |
| i++; |
| break; |
| case ANYTHING_CHAR: |
| result.append(valueChar); |
| i++; |
| break; |
| case ESCAPE_CHAR: |
| // Escape character, check the next character to make sure that |
| // the literals match |
| j++; |
| if (j < length) |
| { |
| maskChar = mask.charAt(j); |
| if (! isPlaceHolder && getValueContainsLiteralCharacters() |
| && valueChar != maskChar) |
| throw new ParseException ("Invalid character: "+ valueChar, i); |
| if (getValueContainsLiteralCharacters()) |
| i++; |
| result.append(maskChar); |
| } |
| break; |
| default: |
| if (! isPlaceHolder && getValueContainsLiteralCharacters() |
| && valueChar != maskChar) |
| throw new ParseException ("Invalid character: "+ valueChar, i); |
| if (getValueContainsLiteralCharacters()) |
| i++; |
| result.append(maskChar); |
| } |
| } |
| return result.toString(); |
| } |
| |
| } |