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