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 gnu.java.lang.CPStringBuilder;
  42: 
  43: import java.text.ParseException;
  44: 
  45: import javax.swing.JFormattedTextField;
  46: 
  47: /**
  48:  * @author Anthony Balkissoon abalkiss at redhat dot com
  49:  *
  50:  */
  51: public class MaskFormatter extends DefaultFormatter
  52: {
  53:   // The declaration of the valid mask characters
  54:   private static final char NUM_CHAR = '#';
  55:   private static final char ESCAPE_CHAR = '\'';
  56:   private static final char UPPERCASE_CHAR = 'U';
  57:   private static final char LOWERCASE_CHAR = 'L';
  58:   private static final char ALPHANUM_CHAR = 'A';
  59:   private static final char LETTER_CHAR = '?';
  60:   private static final char ANYTHING_CHAR = '*';
  61:   private static final char HEX_CHAR = 'H';
  62: 
  63:   /** The mask for this MaskFormatter **/
  64:   private String mask;
  65: 
  66:   /**
  67:    * A String made up of the characters that are not valid for input for
  68:    * this MaskFormatter.
  69:    */
  70:   private String invalidChars;
  71: 
  72:   /**
  73:    * A String made up of the characters that are valid for input for
  74:    * this MaskFormatter.
  75:    */
  76:   private String validChars;
  77: 
  78:   /** A String used in place of missing chracters if the value does not
  79:    * completely fill in the spaces in the mask.
  80:    */
  81:   private String placeHolder;
  82: 
  83:   /** A character used in place of missing characters if the value does
  84:    * not completely fill in the spaces in the mask.
  85:    */
  86:   private char placeHolderChar = ' ';
  87: 
  88:   /**
  89:    * Whether or not stringToValue should return literal characters in the mask.
  90:    */
  91:   private boolean valueContainsLiteralCharacters = true;
  92: 
  93:   /** A String used for easy access to valid HEX characters **/
  94:   private static String hexString = "0123456789abcdefABCDEF";
  95: 
  96:   /** An int to hold the length of the mask, accounting for escaped characters **/
  97:   int maskLength = 0;
  98: 
  99:   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: }