001/* MaskFormatter.java -- 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text; 040 041import gnu.java.lang.CPStringBuilder; 042 043import java.text.ParseException; 044 045import javax.swing.JFormattedTextField; 046 047/** 048 * @author Anthony Balkissoon abalkiss at redhat dot com 049 * 050 */ 051public class MaskFormatter extends DefaultFormatter 052{ 053 // The declaration of the valid mask characters 054 private static final char NUM_CHAR = '#'; 055 private static final char ESCAPE_CHAR = '\''; 056 private static final char UPPERCASE_CHAR = 'U'; 057 private static final char LOWERCASE_CHAR = 'L'; 058 private static final char ALPHANUM_CHAR = 'A'; 059 private static final char LETTER_CHAR = '?'; 060 private static final char ANYTHING_CHAR = '*'; 061 private static final char HEX_CHAR = 'H'; 062 063 /** The mask for this MaskFormatter **/ 064 private String mask; 065 066 /** 067 * A String made up of the characters that are not valid for input for 068 * this MaskFormatter. 069 */ 070 private String invalidChars; 071 072 /** 073 * A String made up of the characters that are valid for input for 074 * this MaskFormatter. 075 */ 076 private String validChars; 077 078 /** A String used in place of missing chracters if the value does not 079 * completely fill in the spaces in the mask. 080 */ 081 private String placeHolder; 082 083 /** A character used in place of missing characters if the value does 084 * not completely fill in the spaces in the mask. 085 */ 086 private char placeHolderChar = ' '; 087 088 /** 089 * Whether or not stringToValue should return literal characters in the mask. 090 */ 091 private boolean valueContainsLiteralCharacters = true; 092 093 /** A String used for easy access to valid HEX characters **/ 094 private static String hexString = "0123456789abcdefABCDEF"; 095 096 /** An int to hold the length of the mask, accounting for escaped characters **/ 097 int maskLength = 0; 098 099 public MaskFormatter () 100 { 101 // Override super's default behaviour, in MaskFormatter the default 102 // is not to allow invalid values 103 setAllowsInvalid(false); 104 } 105 106 /** 107 * Creates a MaskFormatter with the specified mask. 108 * @specnote doesn't actually throw a ParseException although it 109 * is declared to do so 110 * @param mask 111 * @throws java.text.ParseException 112 */ 113 public MaskFormatter (String mask) throws java.text.ParseException 114 { 115 this(); 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 return super.stringToValue(convertStringToValue(value)); 311 } 312 313 private String convertStringToValue(String value) 314 throws ParseException 315 { 316 CPStringBuilder result = new CPStringBuilder(); 317 char valueChar; 318 boolean isPlaceHolder; 319 320 int length = mask.length(); 321 for (int i = 0, j = 0; j < length; j++) 322 { 323 char maskChar = mask.charAt(j); 324 325 if (i < value.length()) 326 { 327 isPlaceHolder = false; 328 valueChar = value.charAt(i); 329 if (maskChar != ESCAPE_CHAR && maskChar != valueChar) 330 { 331 if (invalidChars != null 332 && invalidChars.indexOf(valueChar) != -1) 333 throw new ParseException("Invalid character: " + valueChar, i); 334 if (validChars != null 335 && validChars.indexOf(valueChar) == -1) 336 throw new ParseException("Invalid character: " + valueChar, i); 337 } 338 } 339 else if (placeHolder != null && i < placeHolder.length()) 340 { 341 isPlaceHolder = true; 342 valueChar = placeHolder.charAt(i); 343 } 344 else 345 { 346 isPlaceHolder = true; 347 valueChar = placeHolderChar; 348 } 349 350 // This switch block on the mask character checks that the character 351 // within <code>value</code> at that point is valid according to the 352 // mask and also converts to upper/lowercase as needed. 353 switch (maskChar) 354 { 355 case NUM_CHAR: 356 if (! Character.isDigit(valueChar)) 357 throw new ParseException("Number expected: " + valueChar, i); 358 result.append(valueChar); 359 i++; 360 break; 361 case UPPERCASE_CHAR: 362 if (! Character.isLetter(valueChar)) 363 throw new ParseException("Letter expected", i); 364 result.append(Character.toUpperCase(valueChar)); 365 i++; 366 break; 367 case LOWERCASE_CHAR: 368 if (! Character.isLetter(valueChar)) 369 throw new ParseException("Letter expected", i); 370 result.append(Character.toLowerCase(valueChar)); 371 i++; 372 break; 373 case ALPHANUM_CHAR: 374 if (! Character.isLetterOrDigit(valueChar)) 375 throw new ParseException("Letter or number expected", i); 376 result.append(valueChar); 377 i++; 378 break; 379 case LETTER_CHAR: 380 if (! Character.isLetter(valueChar)) 381 throw new ParseException("Letter expected", i); 382 result.append(valueChar); 383 i++; 384 break; 385 case HEX_CHAR: 386 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) 387 throw new ParseException("Hexadecimal character expected", i); 388 result.append(valueChar); 389 i++; 390 break; 391 case ANYTHING_CHAR: 392 result.append(valueChar); 393 i++; 394 break; 395 case ESCAPE_CHAR: 396 // Escape character, check the next character to make sure that 397 // the literals match 398 j++; 399 if (j < length) 400 { 401 maskChar = mask.charAt(j); 402 if (! isPlaceHolder && getValueContainsLiteralCharacters() 403 && valueChar != maskChar) 404 throw new ParseException ("Invalid character: "+ valueChar, i); 405 if (getValueContainsLiteralCharacters()) 406 { 407 result.append(maskChar); 408 } 409 i++; 410 } 411 else if (! isPlaceHolder) 412 throw new ParseException("Bad match at trailing escape: ", i); 413 break; 414 default: 415 if (! isPlaceHolder && getValueContainsLiteralCharacters() 416 && valueChar != maskChar) 417 throw new ParseException ("Invalid character: "+ valueChar, i); 418 if (getValueContainsLiteralCharacters()) 419 { 420 result.append(maskChar); 421 } 422 i++; 423 } 424 } 425 return result.toString(); 426 } 427 428 /** 429 * Returns a String representation of the Object value based on the mask. 430 * 431 * @param value the value to convert 432 * @throws ParseException if value is invalid for this mask and valid/invalid 433 * character sets 434 */ 435 public String valueToString(Object value) throws ParseException 436 { 437 String string = value != null ? value.toString() : ""; 438 return convertValueToString(string); 439 } 440 441 /** 442 * This method takes in a String and runs it through the mask to make 443 * sure that it is valid. If <code>convert</code> is true, it also 444 * converts letters to upper/lowercase as required by the mask. 445 * @param value the String to convert 446 * @return the converted String 447 * @throws ParseException if the given String isn't valid for the mask 448 */ 449 private String convertValueToString(String value) 450 throws ParseException 451 { 452 CPStringBuilder result = new CPStringBuilder(); 453 char valueChar; 454 boolean isPlaceHolder; 455 456 int length = mask.length(); 457 for (int i = 0, j = 0; j < length; j++) 458 { 459 char maskChar = mask.charAt(j); 460 if (i < value.length()) 461 { 462 isPlaceHolder = false; 463 valueChar = value.charAt(i); 464 if (maskChar != ESCAPE_CHAR && valueChar != maskChar) 465 { 466 if (invalidChars != null 467 && invalidChars.indexOf(valueChar) != -1) 468 throw new ParseException("Invalid character: " + valueChar, 469 i); 470 if (validChars != null && validChars.indexOf(valueChar) == -1) 471 throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar, 472 i); 473 } 474 } 475 else if (placeHolder != null && i < placeHolder.length()) 476 { 477 isPlaceHolder = true; 478 valueChar = placeHolder.charAt(i); 479 } 480 else 481 { 482 isPlaceHolder = true; 483 valueChar = placeHolderChar; 484 } 485 486 // This switch block on the mask character checks that the character 487 // within <code>value</code> at that point is valid according to the 488 // mask and also converts to upper/lowercase as needed. 489 switch (maskChar) 490 { 491 case NUM_CHAR: 492 if ( ! isPlaceHolder && ! Character.isDigit(valueChar)) 493 throw new ParseException("Number expected: " + valueChar, i); 494 result.append(valueChar); 495 i++; 496 break; 497 case UPPERCASE_CHAR: 498 if (! Character.isLetter(valueChar)) 499 throw new ParseException("Letter expected", i); 500 result.append(Character.toUpperCase(valueChar)); 501 i++; 502 break; 503 case LOWERCASE_CHAR: 504 if (! Character.isLetter(valueChar)) 505 throw new ParseException("Letter expected", i); 506 result.append(Character.toLowerCase(valueChar)); 507 i++; 508 break; 509 case ALPHANUM_CHAR: 510 if (! Character.isLetterOrDigit(valueChar)) 511 throw new ParseException("Letter or number expected", i); 512 result.append(valueChar); 513 i++; 514 break; 515 case LETTER_CHAR: 516 if (! Character.isLetter(valueChar)) 517 throw new ParseException("Letter expected", i); 518 result.append(valueChar); 519 i++; 520 break; 521 case HEX_CHAR: 522 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder) 523 throw new ParseException("Hexadecimal character expected", i); 524 result.append(valueChar); 525 i++; 526 break; 527 case ANYTHING_CHAR: 528 result.append(valueChar); 529 i++; 530 break; 531 case ESCAPE_CHAR: 532 // Escape character, check the next character to make sure that 533 // the literals match 534 j++; 535 if (j < length) 536 { 537 maskChar = mask.charAt(j); 538 if (! isPlaceHolder && getValueContainsLiteralCharacters() 539 && valueChar != maskChar) 540 throw new ParseException ("Invalid character: "+ valueChar, i); 541 if (getValueContainsLiteralCharacters()) 542 i++; 543 result.append(maskChar); 544 } 545 break; 546 default: 547 if (! isPlaceHolder && getValueContainsLiteralCharacters() 548 && valueChar != maskChar) 549 throw new ParseException ("Invalid character: "+ valueChar, i); 550 if (getValueContainsLiteralCharacters()) 551 i++; 552 result.append(maskChar); 553 } 554 } 555 return result.toString(); 556 } 557 558}