Frames | No Frames |
1: /* MaskFormatter.java -- 2: Copyright (C) 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.text.ParseException; 42: 43: import javax.swing.JFormattedTextField; 44: 45: /** 46: * @author Anthony Balkissoon abalkiss at redhat dot com 47: * 48: */ 49: public class MaskFormatter extends DefaultFormatter 50: { 51: // The declaration of the valid mask characters 52: private static final char NUM_CHAR = '#'; 53: private static final char ESCAPE_CHAR = '\''; 54: private static final char UPPERCASE_CHAR = 'U'; 55: private static final char LOWERCASE_CHAR = 'L'; 56: private static final char ALPHANUM_CHAR = 'A'; 57: private static final char LETTER_CHAR = '?'; 58: private static final char ANYTHING_CHAR = '*'; 59: private static final char HEX_CHAR = 'H'; 60: 61: /** The mask for this MaskFormatter **/ 62: private String mask; 63: 64: /** 65: * A String made up of the characters that are not valid for input for 66: * this MaskFormatter. 67: */ 68: private String invalidChars; 69: 70: /** 71: * A String made up of the characters that are valid for input for 72: * this MaskFormatter. 73: */ 74: private String validChars; 75: 76: /** A String used in place of missing chracters if the value does not 77: * completely fill in the spaces in the mask. 78: */ 79: private String placeHolder; 80: 81: /** A character used in place of missing characters if the value does 82: * not completely fill in the spaces in the mask. 83: */ 84: private char placeHolderChar = ' '; 85: 86: /** 87: * Whether or not stringToValue should return literal characters in the mask. 88: */ 89: private boolean valueContainsLiteralCharacters = true; 90: 91: /** A String used for easy access to valid HEX characters **/ 92: private static String hexString = "0123456789abcdefABCDEF"; 93: 94: /** An int to hold the length of the mask, accounting for escaped characters **/ 95: int maskLength = 0; 96: 97: public MaskFormatter () 98: { 99: // Override super's default behaviour, in MaskFormatter the default 100: // is not to allow invalid values 101: setAllowsInvalid(false); 102: } 103: 104: /** 105: * Creates a MaskFormatter with the specified mask. 106: * @specnote doesn't actually throw a ParseException although it 107: * is declared to do so 108: * @param mask 109: * @throws java.text.ParseException 110: */ 111: public MaskFormatter (String mask) throws java.text.ParseException 112: { 113: // Override super's default behaviour, in MaskFormatter the default 114: // is not to allow invalid values 115: setAllowsInvalid(false); 116: setMask (mask); 117: } 118: 119: /** 120: * Returns the mask used in this MaskFormatter. 121: * @return the mask used in this MaskFormatter. 122: */ 123: public String getMask() 124: { 125: return mask; 126: } 127: 128: /** 129: * Returns a String containing the characters that are not valid for input 130: * for this MaskFormatter. 131: * @return a String containing the invalid characters. 132: */ 133: public String getInvalidCharacters() 134: { 135: return invalidChars; 136: } 137: 138: /** 139: * Sets characters that are not valid for input. If 140: * <code>invalidCharacters</code> is non-null then no characters contained 141: * in it will be allowed to be input. 142: * 143: * @param invalidCharacters the String specifying invalid characters. 144: */ 145: public void setInvalidCharacters (String invalidCharacters) 146: { 147: this.invalidChars = invalidCharacters; 148: } 149: 150: /** 151: * Returns a String containing the characters that are valid for input 152: * for this MaskFormatter. 153: * @return a String containing the valid characters. 154: */ 155: public String getValidCharacters() 156: { 157: return validChars; 158: } 159: 160: /** 161: * Sets characters that are valid for input. If 162: * <code>validCharacters</code> is non-null then no characters that are 163: * not contained in it will be allowed to be input. 164: * 165: * @param validCharacters the String specifying valid characters. 166: */ 167: public void setValidCharacters (String validCharacters) 168: { 169: this.validChars = validCharacters; 170: } 171: 172: /** 173: * Returns the place holder String that is used in place of missing 174: * characters when the value doesn't completely fill in the spaces 175: * in the mask. 176: * @return the place holder String. 177: */ 178: public String getPlaceholder() 179: { 180: return placeHolder; 181: } 182: 183: /** 184: * Sets the string to use if the value does not completely fill in the mask. 185: * If this is null, the place holder character will be used instead. 186: * @param placeholder the String to use if the value doesn't completely 187: * fill in the mask. 188: */ 189: public void setPlaceholder (String placeholder) 190: { 191: this.placeHolder = placeholder; 192: } 193: 194: /** 195: * Returns the character used in place of missing characters when the 196: * value doesn't completely fill the mask. 197: * @return the place holder character 198: */ 199: public char getPlaceholderCharacter() 200: { 201: return placeHolderChar; 202: } 203: 204: /** 205: * Sets the char to use if the value does not completely fill in the mask. 206: * This is only used if the place holder String has not been set or does 207: * not completely fill in the mask. 208: * @param placeholder the char to use if the value doesn't completely 209: * fill in the mask. 210: */ 211: public void setPlaceholderCharacter (char placeholder) 212: { 213: this.placeHolderChar = placeholder; 214: } 215: 216: /** 217: * Returns true if stringToValue should return the literal 218: * characters in the mask. 219: * @return true if stringToValue should return the literal 220: * characters in the mask 221: */ 222: public boolean getValueContainsLiteralCharacters() 223: { 224: return valueContainsLiteralCharacters; 225: } 226: 227: /** 228: * Determines whether stringToValue will return literal characters or not. 229: * @param containsLiteralChars if true, stringToValue will return the 230: * literal characters in the mask, otherwise it will not. 231: */ 232: public void setValueContainsLiteralCharacters (boolean containsLiteralChars) 233: { 234: this.valueContainsLiteralCharacters = containsLiteralChars; 235: } 236: 237: /** 238: * Sets the mask for this MaskFormatter. 239: * @specnote doesn't actually throw a ParseException even though it is 240: * declared to do so 241: * @param mask the new mask for this MaskFormatter 242: * @throws ParseException if <code>mask</code> is not valid. 243: */ 244: public void setMask (String mask) throws ParseException 245: { 246: this.mask = mask; 247: 248: // Update the cached maskLength. 249: int end = mask.length() - 1; 250: maskLength = 0; 251: for (int i = 0; i <= end; i++) 252: { 253: // Handle escape characters properly - they don't add to the maskLength 254: // but 2 escape characters in a row is really one escape character and 255: // one literal single quote, so that does add 1 to the maskLength. 256: if (mask.charAt(i) == '\'') 257: { 258: // Escape characters at the end of the mask don't do anything. 259: if (i != end) 260: maskLength++; 261: i++; 262: } 263: else 264: maskLength++; 265: } 266: } 267: 268: /** 269: * Installs this MaskFormatter on the JFormattedTextField. 270: * Invokes valueToString to convert the current value from the 271: * JFormattedTextField to a String, then installs the Actions from 272: * getActions, the DocumentFilter from getDocumentFilter, and the 273: * NavigationFilter from getNavigationFilter. 274: * 275: * If valueToString throws a ParseException, this method sets the text 276: * to an empty String and marks the JFormattedTextField as invalid. 277: */ 278: public void install (JFormattedTextField ftf) 279: { 280: super.install(ftf); 281: if (ftf != null) 282: { 283: try 284: { 285: valueToString(ftf.getValue()); 286: } 287: catch (ParseException pe) 288: { 289: // Set the text to an empty String and mark the JFormattedTextField 290: // as invalid. 291: ftf.setText(""); 292: setEditValid(false); 293: } 294: } 295: } 296: 297: /** 298: * Parses the text using the mask, valid characters, and invalid characters 299: * to determine the appropriate Object to return. This strips the literal 300: * characters if necessary and invokes super.stringToValue. If the paramter 301: * is invalid for the current mask and valid/invalid character sets this 302: * method will throw a ParseException. 303: * 304: * @param value the String to parse 305: * @throws ParseException if value doesn't match the mask and valid/invalid 306: * character sets 307: */ 308: public Object stringToValue (String value) throws ParseException 309: { 310: int vLength = value.length(); 311: 312: // For value to be a valid it must be the same length as the mask 313: // note this doesn't take into account symbols that occupy more than 314: // one character, this is something we may possibly need to fix. 315: if (maskLength != vLength) 316: throw new ParseException ("stringToValue passed invalid value", vLength); 317: 318: // Check if the value is valid according to the mask and valid/invalid 319: // sets. 320: try 321: { 322: convertValue(value, false); 323: } 324: catch (ParseException pe) 325: { 326: throw new ParseException("stringToValue passed invalid value", 327: pe.getErrorOffset()); 328: } 329: 330: if (!getValueContainsLiteralCharacters()) 331: value = stripLiterals(value); 332: return super.stringToValue(value); 333: } 334: 335: /** 336: * Strips the literal characters from the given String. 337: * @param value the String to strip 338: * @return the stripped String 339: */ 340: String stripLiterals(String value) 341: { 342: StringBuffer result = new StringBuffer(); 343: for (int i = 0; i < value.length(); i++) 344: { 345: // Only append the characters that don't correspond to literal 346: // characters in the mask. 347: switch (mask.charAt(i)) 348: { 349: case NUM_CHAR: 350: case UPPERCASE_CHAR: 351: case LOWERCASE_CHAR: 352: case ALPHANUM_CHAR: 353: case LETTER_CHAR: 354: case HEX_CHAR: 355: case ANYTHING_CHAR: 356: result.append(value.charAt(i)); 357: break; 358: default: 359: } 360: } 361: return result.toString(); 362: } 363: 364: /** 365: * Returns a String representation of the Object value based on the mask. 366: * 367: * @param value the value to convert 368: * @throws ParseException if value is invalid for this mask and valid/invalid 369: * character sets 370: */ 371: public String valueToString (Object value) throws ParseException 372: { 373: String result = super.valueToString(value); 374: int rLength = result.length(); 375: 376: // If value is longer than the mask, truncate it. Note we may need to 377: // account for symbols that are more than one character long. 378: if (rLength > maskLength) 379: result = result.substring(0, maskLength); 380: 381: // Verify the validity and convert to upper/lowercase as needed. 382: result = convertValue(result, true); 383: if (rLength < maskLength) 384: return pad(result, rLength); 385: return result; 386: } 387: 388: /** 389: * This method takes in a String and runs it through the mask to make 390: * sure that it is valid. If <code>convert</code> is true, it also 391: * converts letters to upper/lowercase as required by the mask. 392: * @param value the String to convert 393: * @param convert true if we should convert letters to upper/lowercase 394: * @return the converted String 395: * @throws ParseException if the given String isn't valid for the mask 396: */ 397: String convertValue(String value, boolean convert) throws ParseException 398: { 399: StringBuffer result = new StringBuffer(value); 400: char markChar; 401: char resultChar; 402: boolean literal; 403: 404: // this boolean is specifically to avoid calling the isCharValid method 405: // when neither invalidChars or validChars has been set 406: boolean checkCharSets = (invalidChars != null || validChars != null); 407: 408: for (int i = 0, j = 0; i < value.length(); i++, j++) 409: { 410: literal = false; 411: resultChar = result.charAt(i); 412: // This switch block on the mask character checks that the character 413: // within <code>value</code> at that point is valid according to the 414: // mask and also converts to upper/lowercase as needed. 415: switch (mask.charAt(j)) 416: { 417: case NUM_CHAR: 418: if (!Character.isDigit(resultChar)) 419: throw new ParseException("Number expected", i); 420: break; 421: case UPPERCASE_CHAR: 422: if (!Character.isLetter(resultChar)) 423: throw new ParseException("Letter expected", i); 424: if (convert) 425: result.setCharAt(i, Character.toUpperCase(resultChar)); 426: break; 427: case LOWERCASE_CHAR: 428: if (!Character.isLetter(resultChar)) 429: throw new ParseException("Letter expected", i); 430: if (convert) 431: result.setCharAt(i, Character.toLowerCase(resultChar)); 432: break; 433: case ALPHANUM_CHAR: 434: if (!Character.isLetterOrDigit(resultChar)) 435: throw new ParseException("Letter or number expected", i); 436: break; 437: case LETTER_CHAR: 438: if (!Character.isLetter(resultChar)) 439: throw new ParseException("Letter expected", i); 440: break; 441: case HEX_CHAR: 442: if (hexString.indexOf(resultChar) == -1) 443: throw new ParseException("Hexadecimal character expected", i); 444: break; 445: case ANYTHING_CHAR: 446: break; 447: case ESCAPE_CHAR: 448: // Escape character, check the next character to make sure that 449: // the literals match 450: j++; 451: literal = true; 452: if (resultChar != mask.charAt(j)) 453: throw new ParseException ("Invalid character: "+resultChar, i); 454: break; 455: default: 456: literal = true; 457: if (!getValueContainsLiteralCharacters() && convert) 458: throw new ParseException ("Invalid character: "+resultChar, i); 459: else if (resultChar != mask.charAt(j)) 460: throw new ParseException ("Invalid character: "+resultChar, i); 461: } 462: // If necessary, check if the character is valid. 463: if (!literal && checkCharSets && !isCharValid(resultChar)) 464: throw new ParseException("invalid character: "+resultChar, i); 465: 466: } 467: return result.toString(); 468: } 469: 470: /** 471: * Convenience method used by many other methods to check if a character is 472: * valid according to the mask, the validChars, and the invalidChars. To 473: * be valid a character must: 474: * 1. be allowed by the mask 475: * 2. be present in any non-null validChars String 476: * 3. not be present in any non-null invalidChars String 477: * @param testChar the character to test 478: * @return true if the character is valid 479: */ 480: boolean isCharValid(char testChar) 481: { 482: char lower = Character.toLowerCase(testChar); 483: char upper = Character.toUpperCase(testChar); 484: // If validChars isn't null, the character must appear in it. 485: if (validChars != null) 486: if (validChars.indexOf(lower) == -1 && validChars.indexOf(upper) == -1) 487: return false; 488: // If invalidChars isn't null, the character must not appear in it. 489: if (invalidChars != null) 490: if (invalidChars.indexOf(lower) != -1 491: || invalidChars.indexOf(upper) != -1) 492: return false; 493: return true; 494: } 495: 496: /** 497: * Pads the value with literals, the placeholder String and/or placeholder 498: * character as appropriate. 499: * @param value the value to pad 500: * @param currLength the current length of the value 501: * @return the padded String 502: */ 503: String pad (String value, int currLength) 504: { 505: StringBuffer result = new StringBuffer(value); 506: int index = currLength; 507: while (result.length() < maskLength) 508: { 509: // The character used to pad may be a literal, a character from the 510: // place holder string, or the place holder character. getPadCharAt 511: // will find the proper one for us. 512: result.append (getPadCharAt(index)); 513: index++; 514: } 515: return result.toString(); 516: } 517: 518: /** 519: * Returns the character with which to pad the value at the given index 520: * position. If the mask has a literal at this position, this is returned 521: * otherwise if the place holder string is initialized and is longer than 522: * <code>i</code> characters then the character at position <code>i</code> 523: * from this String is returned. Else, the place holder character is 524: * returned. 525: * @param i the index at which we want to pad the value 526: * @return the character with which we should pad the value 527: */ 528: char getPadCharAt(int i) 529: { 530: boolean escaped = false; 531: int target = i; 532: char maskChar; 533: int holderLength = placeHolder == null ? -1 : placeHolder.length(); 534: // We must iterate through the mask from the beginning, because the given 535: // index doesn't account for escaped characters. For example, with the 536: // mask "1A'A''A1" index 2 refers to the literalized A, not to the 537: // single quotation. 538: for (int n = 0; n < mask.length(); n++) 539: { 540: maskChar = mask.charAt(n); 541: if (maskChar == ESCAPE_CHAR && !escaped) 542: { 543: target++; 544: escaped = true; 545: } 546: else if (escaped == true) 547: { 548: // Check if target == n which means we've come to the character 549: // we want to return and since it is a literal (because escaped 550: // is true), we return it. 551: if (target == n) 552: return maskChar; 553: escaped = false; 554: } 555: if (target == n) 556: { 557: // We've come to the character we want to return. It wasn't 558: // escaped so if it isn't a literal we should return either 559: // the character from place holder string or the place holder 560: // character, depending on whether or not the place holder 561: // string is long enough. 562: switch (maskChar) 563: { 564: case NUM_CHAR: 565: case UPPERCASE_CHAR: 566: case LOWERCASE_CHAR: 567: case ALPHANUM_CHAR: 568: case LETTER_CHAR: 569: case HEX_CHAR: 570: case ANYTHING_CHAR: 571: if (holderLength > i) 572: return placeHolder.charAt(i); 573: else 574: return placeHolderChar; 575: default: 576: return maskChar; 577: } 578: } 579: } 580: // This shouldn't happen 581: throw new AssertionError("MaskFormatter.getMaskCharAt failed"); 582: } 583: }