Source for javax.swing.text.MaskFormatter

   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:     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: }