001 /* MaskFormatter.java -- 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text; 040 041 import java.text.ParseException; 042 043 import javax.swing.JFormattedTextField; 044 045 /** 046 * @author Anthony Balkissoon abalkiss at redhat dot com 047 * 048 */ 049 public class MaskFormatter extends DefaultFormatter 050 { 051 // The declaration of the valid mask characters 052 private static final char NUM_CHAR = '#'; 053 private static final char ESCAPE_CHAR = '\''; 054 private static final char UPPERCASE_CHAR = 'U'; 055 private static final char LOWERCASE_CHAR = 'L'; 056 private static final char ALPHANUM_CHAR = 'A'; 057 private static final char LETTER_CHAR = '?'; 058 private static final char ANYTHING_CHAR = '*'; 059 private static final char HEX_CHAR = 'H'; 060 061 /** The mask for this MaskFormatter **/ 062 private String mask; 063 064 /** 065 * A String made up of the characters that are not valid for input for 066 * this MaskFormatter. 067 */ 068 private String invalidChars; 069 070 /** 071 * A String made up of the characters that are valid for input for 072 * this MaskFormatter. 073 */ 074 private String validChars; 075 076 /** A String used in place of missing chracters if the value does not 077 * completely fill in the spaces in the mask. 078 */ 079 private String placeHolder; 080 081 /** A character used in place of missing characters if the value does 082 * not completely fill in the spaces in the mask. 083 */ 084 private char placeHolderChar = ' '; 085 086 /** 087 * Whether or not stringToValue should return literal characters in the mask. 088 */ 089 private boolean valueContainsLiteralCharacters = true; 090 091 /** A String used for easy access to valid HEX characters **/ 092 private static String hexString = "0123456789abcdefABCDEF"; 093 094 /** An int to hold the length of the mask, accounting for escaped characters **/ 095 int maskLength = 0; 096 097 public MaskFormatter () 098 { 099 // 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 this(); 114 setMask (mask); 115 } 116 117 /** 118 * Returns the mask used in this MaskFormatter. 119 * @return the mask used in this MaskFormatter. 120 */ 121 public String getMask() 122 { 123 return mask; 124 } 125 126 /** 127 * Returns a String containing the characters that are not valid for input 128 * for this MaskFormatter. 129 * @return a String containing the invalid characters. 130 */ 131 public String getInvalidCharacters() 132 { 133 return invalidChars; 134 } 135 136 /** 137 * Sets characters that are not valid for input. If 138 * <code>invalidCharacters</code> is non-null then no characters contained 139 * in it will be allowed to be input. 140 * 141 * @param invalidCharacters the String specifying invalid characters. 142 */ 143 public void setInvalidCharacters (String invalidCharacters) 144 { 145 this.invalidChars = invalidCharacters; 146 } 147 148 /** 149 * Returns a String containing the characters that are valid for input 150 * for this MaskFormatter. 151 * @return a String containing the valid characters. 152 */ 153 public String getValidCharacters() 154 { 155 return validChars; 156 } 157 158 /** 159 * Sets characters that are valid for input. If 160 * <code>validCharacters</code> is non-null then no characters that are 161 * not contained in it will be allowed to be input. 162 * 163 * @param validCharacters the String specifying valid characters. 164 */ 165 public void setValidCharacters (String validCharacters) 166 { 167 this.validChars = validCharacters; 168 } 169 170 /** 171 * Returns the place holder String that is used in place of missing 172 * characters when the value doesn't completely fill in the spaces 173 * in the mask. 174 * @return the place holder String. 175 */ 176 public String getPlaceholder() 177 { 178 return placeHolder; 179 } 180 181 /** 182 * Sets the string to use if the value does not completely fill in the mask. 183 * If this is null, the place holder character will be used instead. 184 * @param placeholder the String to use if the value doesn't completely 185 * fill in the mask. 186 */ 187 public void setPlaceholder (String placeholder) 188 { 189 this.placeHolder = placeholder; 190 } 191 192 /** 193 * Returns the character used in place of missing characters when the 194 * value doesn't completely fill the mask. 195 * @return the place holder character 196 */ 197 public char getPlaceholderCharacter() 198 { 199 return placeHolderChar; 200 } 201 202 /** 203 * Sets the char to use if the value does not completely fill in the mask. 204 * This is only used if the place holder String has not been set or does 205 * not completely fill in the mask. 206 * @param placeholder the char to use if the value doesn't completely 207 * fill in the mask. 208 */ 209 public void setPlaceholderCharacter (char placeholder) 210 { 211 this.placeHolderChar = placeholder; 212 } 213 214 /** 215 * Returns true if stringToValue should return the literal 216 * characters in the mask. 217 * @return true if stringToValue should return the literal 218 * characters in the mask 219 */ 220 public boolean getValueContainsLiteralCharacters() 221 { 222 return valueContainsLiteralCharacters; 223 } 224 225 /** 226 * Determines whether stringToValue will return literal characters or not. 227 * @param containsLiteralChars if true, stringToValue will return the 228 * literal characters in the mask, otherwise it will not. 229 */ 230 public void setValueContainsLiteralCharacters (boolean containsLiteralChars) 231 { 232 this.valueContainsLiteralCharacters = containsLiteralChars; 233 } 234 235 /** 236 * Sets the mask for this MaskFormatter. 237 * @specnote doesn't actually throw a ParseException even though it is 238 * declared to do so 239 * @param mask the new mask for this MaskFormatter 240 * @throws ParseException if <code>mask</code> is not valid. 241 */ 242 public void setMask (String mask) throws ParseException 243 { 244 this.mask = mask; 245 246 // Update the cached maskLength. 247 int end = mask.length() - 1; 248 maskLength = 0; 249 for (int i = 0; i <= end; i++) 250 { 251 // Handle escape characters properly - they don't add to the maskLength 252 // but 2 escape characters in a row is really one escape character and 253 // one literal single quote, so that does add 1 to the maskLength. 254 if (mask.charAt(i) == '\'') 255 { 256 // Escape characters at the end of the mask don't do anything. 257 if (i != end) 258 maskLength++; 259 i++; 260 } 261 else 262 maskLength++; 263 } 264 } 265 266 /** 267 * Installs this MaskFormatter on the JFormattedTextField. 268 * Invokes valueToString to convert the current value from the 269 * JFormattedTextField to a String, then installs the Actions from 270 * getActions, the DocumentFilter from getDocumentFilter, and the 271 * NavigationFilter from getNavigationFilter. 272 * 273 * If valueToString throws a ParseException, this method sets the text 274 * to an empty String and marks the JFormattedTextField as invalid. 275 */ 276 public void install (JFormattedTextField ftf) 277 { 278 super.install(ftf); 279 if (ftf != null) 280 { 281 try 282 { 283 valueToString(ftf.getValue()); 284 } 285 catch (ParseException pe) 286 { 287 // Set the text to an empty String and mark the JFormattedTextField 288 // as invalid. 289 ftf.setText(""); 290 setEditValid(false); 291 } 292 } 293 } 294 295 /** 296 * Parses the text using the mask, valid characters, and invalid characters 297 * to determine the appropriate Object to return. This strips the literal 298 * characters if necessary and invokes super.stringToValue. If the paramter 299 * is invalid for the current mask and valid/invalid character sets this 300 * method will throw a ParseException. 301 * 302 * @param value the String to parse 303 * @throws ParseException if value doesn't match the mask and valid/invalid 304 * character sets 305 */ 306 public Object stringToValue (String value) throws ParseException 307 { 308 return super.stringToValue(convertStringToValue(value)); 309 } 310 311 private String convertStringToValue(String value) 312 throws ParseException 313 { 314 StringBuffer result = new StringBuffer(); 315 char valueChar; 316 boolean isPlaceHolder; 317 318 int length = mask.length(); 319 for (int i = 0, j = 0; j < length; j++) 320 { 321 char maskChar = mask.charAt(j); 322 323 if (i < value.length()) 324 { 325 isPlaceHolder = false; 326 valueChar = value.charAt(i); 327 if (maskChar != ESCAPE_CHAR && maskChar != valueChar) 328 { 329 if (invalidChars != null 330 && invalidChars.indexOf(valueChar) != -1) 331 throw new ParseException("Invalid character: " + valueChar, i); 332 if (validChars != null 333 && validChars.indexOf(valueChar) == -1) 334 throw new ParseException("Invalid character: " + valueChar, i); 335 } 336 } 337 else if (placeHolder != null && i < placeHolder.length()) 338 { 339 isPlaceHolder = true; 340 valueChar = placeHolder.charAt(i); 341 } 342 else 343 { 344 isPlaceHolder = true; 345 valueChar = placeHolderChar; 346 } 347 348 // This switch block on the mask character checks that the character 349 // within <code>value</code> at that point is valid according to the 350 // mask and also converts to upper/lowercase as needed. 351 switch (maskChar) 352 { 353 case NUM_CHAR: 354 if (! Character.isDigit(valueChar)) 355 throw new ParseException("Number expected: " + valueChar, i); 356 result.append(valueChar); 357 i++; 358 break; 359 case UPPERCASE_CHAR: 360 if (! Character.isLetter(valueChar)) 361 throw new ParseException("Letter expected", i); 362 result.append(Character.toUpperCase(valueChar)); 363 i++; 364 break; 365 case LOWERCASE_CHAR: 366 if (! Character.isLetter(valueChar)) 367 throw new ParseException("Letter expected", i); 368 result.append(Character.toLowerCase(valueChar)); 369 i++; 370 break; 371 case ALPHANUM_CHAR: 372 if (! Character.isLetterOrDigit(valueChar)) 373 throw new ParseException("Letter or number expected", i); 374 result.append(valueChar); 375 i++; 376 break; 377 case LETTER_CHAR: 378 if (! Character.isLetter(valueChar)) 379 throw new ParseException("Letter expected", i); 380 result.append(valueChar); 381 i++; 382 break; 383 case HEX_CHAR: 384 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) 385 throw new ParseException("Hexadecimal character expected", i); 386 result.append(valueChar); 387 i++; 388 break; 389 case ANYTHING_CHAR: 390 result.append(valueChar); 391 i++; 392 break; 393 case ESCAPE_CHAR: 394 // Escape character, check the next character to make sure that 395 // the literals match 396 j++; 397 if (j < length) 398 { 399 maskChar = mask.charAt(j); 400 if (! isPlaceHolder && getValueContainsLiteralCharacters() 401 && valueChar != maskChar) 402 throw new ParseException ("Invalid character: "+ valueChar, i); 403 if (getValueContainsLiteralCharacters()) 404 { 405 result.append(maskChar); 406 } 407 i++; 408 } 409 else if (! isPlaceHolder) 410 throw new ParseException("Bad match at trailing escape: ", i); 411 break; 412 default: 413 if (! isPlaceHolder && getValueContainsLiteralCharacters() 414 && valueChar != maskChar) 415 throw new ParseException ("Invalid character: "+ valueChar, i); 416 if (getValueContainsLiteralCharacters()) 417 { 418 result.append(maskChar); 419 } 420 i++; 421 } 422 } 423 return result.toString(); 424 } 425 426 /** 427 * Returns a String representation of the Object value based on the mask. 428 * 429 * @param value the value to convert 430 * @throws ParseException if value is invalid for this mask and valid/invalid 431 * character sets 432 */ 433 public String valueToString(Object value) throws ParseException 434 { 435 String string = value != null ? value.toString() : ""; 436 return convertValueToString(string); 437 } 438 439 /** 440 * This method takes in a String and runs it through the mask to make 441 * sure that it is valid. If <code>convert</code> is true, it also 442 * converts letters to upper/lowercase as required by the mask. 443 * @param value the String to convert 444 * @return the converted String 445 * @throws ParseException if the given String isn't valid for the mask 446 */ 447 private String convertValueToString(String value) 448 throws ParseException 449 { 450 StringBuffer result = new StringBuffer(); 451 char valueChar; 452 boolean isPlaceHolder; 453 454 int length = mask.length(); 455 for (int i = 0, j = 0; j < length; j++) 456 { 457 char maskChar = mask.charAt(j); 458 if (i < value.length()) 459 { 460 isPlaceHolder = false; 461 valueChar = value.charAt(i); 462 if (maskChar != ESCAPE_CHAR && valueChar != maskChar) 463 { 464 if (invalidChars != null 465 && invalidChars.indexOf(valueChar) != -1) 466 throw new ParseException("Invalid character: " + valueChar, 467 i); 468 if (validChars != null && validChars.indexOf(valueChar) == -1) 469 throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar, 470 i); 471 } 472 } 473 else if (placeHolder != null && i < placeHolder.length()) 474 { 475 isPlaceHolder = true; 476 valueChar = placeHolder.charAt(i); 477 } 478 else 479 { 480 isPlaceHolder = true; 481 valueChar = placeHolderChar; 482 } 483 484 // This switch block on the mask character checks that the character 485 // within <code>value</code> at that point is valid according to the 486 // mask and also converts to upper/lowercase as needed. 487 switch (maskChar) 488 { 489 case NUM_CHAR: 490 if ( ! isPlaceHolder && ! Character.isDigit(valueChar)) 491 throw new ParseException("Number expected: " + valueChar, i); 492 result.append(valueChar); 493 i++; 494 break; 495 case UPPERCASE_CHAR: 496 if (! Character.isLetter(valueChar)) 497 throw new ParseException("Letter expected", i); 498 result.append(Character.toUpperCase(valueChar)); 499 i++; 500 break; 501 case LOWERCASE_CHAR: 502 if (! Character.isLetter(valueChar)) 503 throw new ParseException("Letter expected", i); 504 result.append(Character.toLowerCase(valueChar)); 505 i++; 506 break; 507 case ALPHANUM_CHAR: 508 if (! Character.isLetterOrDigit(valueChar)) 509 throw new ParseException("Letter or number expected", i); 510 result.append(valueChar); 511 i++; 512 break; 513 case LETTER_CHAR: 514 if (! Character.isLetter(valueChar)) 515 throw new ParseException("Letter expected", i); 516 result.append(valueChar); 517 i++; 518 break; 519 case HEX_CHAR: 520 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) 521 throw new ParseException("Hexadecimal character expected", i); 522 result.append(valueChar); 523 i++; 524 break; 525 case ANYTHING_CHAR: 526 result.append(valueChar); 527 i++; 528 break; 529 case ESCAPE_CHAR: 530 // Escape character, check the next character to make sure that 531 // the literals match 532 j++; 533 if (j < length) 534 { 535 maskChar = mask.charAt(j); 536 if (! isPlaceHolder && getValueContainsLiteralCharacters() 537 && valueChar != maskChar) 538 throw new ParseException ("Invalid character: "+ valueChar, i); 539 if (getValueContainsLiteralCharacters()) 540 i++; 541 result.append(maskChar); 542 } 543 break; 544 default: 545 if (! isPlaceHolder && getValueContainsLiteralCharacters() 546 && valueChar != maskChar) 547 throw new ParseException ("Invalid character: "+ valueChar, i); 548 if (getValueContainsLiteralCharacters()) 549 i++; 550 result.append(maskChar); 551 } 552 } 553 return result.toString(); 554 } 555 556 }