MaskFormatter.java 18.3 KB
Newer Older
1
/* MaskFormatter.java --
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing.text;

41 42
import gnu.java.lang.CPStringBuilder;

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
import java.text.ParseException;

import javax.swing.JFormattedTextField;

/**
 * @author Anthony Balkissoon abalkiss at redhat dot com
 *
 */
public class MaskFormatter extends DefaultFormatter
{
  // The declaration of the valid mask characters
  private static final char NUM_CHAR = '#';
  private static final char ESCAPE_CHAR = '\'';
  private static final char UPPERCASE_CHAR = 'U';
  private static final char LOWERCASE_CHAR = 'L';
  private static final char ALPHANUM_CHAR = 'A';
  private static final char LETTER_CHAR = '?';
  private static final char ANYTHING_CHAR = '*';
  private static final char HEX_CHAR = 'H';
62

63 64
  /** The mask for this MaskFormatter **/
  private String mask;
65 66 67 68

  /**
   * A String made up of the characters that are not valid for input for
   * this MaskFormatter.
69 70
   */
  private String invalidChars;
71 72 73 74

  /**
   * A String made up of the characters that are valid for input for
   * this MaskFormatter.
75 76
   */
  private String validChars;
77 78

  /** A String used in place of missing chracters if the value does not
79 80 81
   * completely fill in the spaces in the mask.
   */
  private String placeHolder;
82 83

  /** A character used in place of missing characters if the value does
84 85 86
   * not completely fill in the spaces in the mask.
   */
  private char placeHolderChar = ' ';
87

88 89 90 91
  /**
   * Whether or not stringToValue should return literal characters in the mask.
   */
  private boolean valueContainsLiteralCharacters = true;
92

93 94
  /** A String used for easy access to valid HEX characters **/
  private static String hexString = "0123456789abcdefABCDEF";
95

96 97
  /** An int to hold the length of the mask, accounting for escaped characters **/
  int maskLength = 0;
98

99 100 101 102 103 104
  public MaskFormatter ()
  {
    // Override super's default behaviour, in MaskFormatter the default
    // is not to allow invalid values
    setAllowsInvalid(false);
  }
105

106 107
  /**
   * Creates a MaskFormatter with the specified mask.
108
   * @specnote doesn't actually throw a ParseException although it
109 110 111 112 113 114
   * is declared to do so
   * @param mask
   * @throws java.text.ParseException
   */
  public MaskFormatter (String mask) throws java.text.ParseException
  {
115
    this();
116 117
    setMask (mask);
  }
118

119 120 121 122 123 124 125 126
  /**
   * Returns the mask used in this MaskFormatter.
   * @return the mask used in this MaskFormatter.
   */
  public String getMask()
  {
    return mask;
  }
127

128 129 130 131 132 133 134 135 136
  /**
   * Returns a String containing the characters that are not valid for input
   * for this MaskFormatter.
   * @return a String containing the invalid characters.
   */
  public String getInvalidCharacters()
  {
    return invalidChars;
  }
137

138 139 140 141
  /**
   * Sets characters that are not valid for input. If
   * <code>invalidCharacters</code> is non-null then no characters contained
   * in it will be allowed to be input.
142
   *
143 144 145 146 147 148
   * @param invalidCharacters the String specifying invalid characters.
   */
  public void setInvalidCharacters (String invalidCharacters)
  {
    this.invalidChars = invalidCharacters;
  }
149

150 151 152 153 154 155 156 157 158
  /**
   * Returns a String containing the characters that are valid for input
   * for this MaskFormatter.
   * @return a String containing the valid characters.
   */
  public String getValidCharacters()
  {
    return validChars;
  }
159

160 161 162 163
  /**
   * Sets characters that are valid for input. If
   * <code>validCharacters</code> is non-null then no characters that are
   * not contained in it will be allowed to be input.
164
   *
165 166 167 168 169 170 171 172
   * @param validCharacters the String specifying valid characters.
   */
  public void setValidCharacters (String validCharacters)
  {
    this.validChars = validCharacters;
  }

  /**
173
   * Returns the place holder String that is used in place of missing
174 175 176 177 178 179 180 181
   * characters when the value doesn't completely fill in the spaces
   * in the mask.
   * @return the place holder String.
   */
  public String getPlaceholder()
  {
    return placeHolder;
  }
182

183 184 185
  /**
   * Sets the string to use if the value does not completely fill in the mask.
   * 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 188 189 190 191 192
   * fill in the mask.
   */
  public void setPlaceholder (String placeholder)
  {
    this.placeHolder = placeholder;
  }
193

194 195 196 197 198 199 200 201 202
  /**
   * Returns the character used in place of missing characters when the
   * value doesn't completely fill the mask.
   * @return the place holder character
   */
  public char getPlaceholderCharacter()
  {
    return placeHolderChar;
  }
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 210 211 212 213 214
   * fill in the mask.
   */
  public void setPlaceholderCharacter (char placeholder)
  {
    this.placeHolderChar = placeholder;
  }
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 221 222 223 224 225
   * characters in the mask
   */
  public boolean getValueContainsLiteralCharacters()
  {
    return valueContainsLiteralCharacters;
  }
226

227 228
  /**
   * Determines whether stringToValue will return literal characters or not.
229
   * @param containsLiteralChars if true, stringToValue will return the
230 231 232 233 234 235
   * literal characters in the mask, otherwise it will not.
   */
  public void setValueContainsLiteralCharacters (boolean containsLiteralChars)
  {
    this.valueContainsLiteralCharacters = containsLiteralChars;
  }
236

237
  /**
238
   * Sets the mask for this MaskFormatter.
239 240 241 242 243 244 245 246 247 248 249
   * @specnote doesn't actually throw a ParseException even though it is
   * declared to do so
   * @param mask the new mask for this MaskFormatter
   * @throws ParseException if <code>mask</code> is not valid.
   */
  public void setMask (String mask) throws ParseException
  {
    this.mask = mask;

    // Update the cached maskLength.
    int end = mask.length() - 1;
250
    maskLength = 0;
251 252 253 254 255 256
    for (int i = 0; i <= end; i++)
      {
        // Handle escape characters properly - they don't add to the maskLength
        // but 2 escape characters in a row is really one escape character and
        // one literal single quote, so that does add 1 to the maskLength.
        if (mask.charAt(i) == '\'')
257
          {
258 259 260 261 262 263 264 265 266
            // Escape characters at the end of the mask don't do anything.
            if (i != end)
              maskLength++;
            i++;
          }
        else
          maskLength++;
      }
  }
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 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
   * If valueToString throws a ParseException, this method sets the text
   * to an empty String and marks the JFormattedTextField as invalid.
   */
  public void install (JFormattedTextField ftf)
  {
    super.install(ftf);
    if (ftf != null)
      {
        try
        {
          valueToString(ftf.getValue());
        }
        catch (ParseException pe)
        {
          // Set the text to an empty String and mark the JFormattedTextField
          // as invalid.
          ftf.setText("");
          setEditValid(false);
        }
      }
  }
296

297 298 299 300
  /**
   * Parses the text using the mask, valid characters, and invalid characters
   * to determine the appropriate Object to return.  This strips the literal
   * 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 305 306 307 308 309
   * @param value the String to parse
   * @throws ParseException if value doesn't match the mask and valid/invalid
   * character sets
   */
  public Object stringToValue (String value) throws ParseException
  {
310
    return super.stringToValue(convertStringToValue(value));
311
  }
312

313 314
  private String convertStringToValue(String value)
    throws ParseException
315
  {
316
    CPStringBuilder result = new CPStringBuilder();
317 318 319 320 321
    char valueChar;
    boolean isPlaceHolder;

    int length = mask.length();
    for (int i = 0, j = 0; j < length; j++)
322
      {
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
        char maskChar = mask.charAt(j);

        if (i < value.length())
          {
            isPlaceHolder = false;
            valueChar = value.charAt(i);
            if (maskChar != ESCAPE_CHAR && maskChar != valueChar)
              {
                if (invalidChars != null
                    && invalidChars.indexOf(valueChar) != -1)
                  throw new ParseException("Invalid character: " + valueChar, i);
                if (validChars != null
                    && validChars.indexOf(valueChar) == -1)
                  throw new ParseException("Invalid character: " + valueChar, i);
              }
          }
        else if (placeHolder != null && i < placeHolder.length())
          {
            isPlaceHolder = true;
            valueChar = placeHolder.charAt(i);
          }
        else
          {
            isPlaceHolder = true;
            valueChar = placeHolderChar;
          }

350
        // This switch block on the mask character checks that the character
351 352 353
        // within <code>value</code> at that point is valid according to the
        // mask and also converts to upper/lowercase as needed.
        switch (maskChar)
354 355
          {
          case NUM_CHAR:
356 357 358 359 360
            if (! Character.isDigit(valueChar))
              throw new ParseException("Number expected: " + valueChar, i);
            result.append(valueChar);
            i++;
            break;
361
          case UPPERCASE_CHAR:
362 363 364 365 366
            if (! Character.isLetter(valueChar))
              throw new ParseException("Letter expected", i);
            result.append(Character.toUpperCase(valueChar));
            i++;
            break;
367
          case LOWERCASE_CHAR:
368 369 370 371 372
            if (! Character.isLetter(valueChar))
              throw new ParseException("Letter expected", i);
            result.append(Character.toLowerCase(valueChar));
            i++;
            break;
373
          case ALPHANUM_CHAR:
374 375 376 377 378
            if (! Character.isLetterOrDigit(valueChar))
              throw new ParseException("Letter or number expected", i);
            result.append(valueChar);
            i++;
            break;
379
          case LETTER_CHAR:
380 381 382 383 384
            if (! Character.isLetter(valueChar))
              throw new ParseException("Letter expected", i);
            result.append(valueChar);
            i++;
            break;
385
          case HEX_CHAR:
386 387 388 389 390
            if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
              throw new ParseException("Hexadecimal character expected", i);
            result.append(valueChar);
            i++;
            break;
391
          case ANYTHING_CHAR:
392 393 394 395
            result.append(valueChar);
            i++;
            break;
          case ESCAPE_CHAR:
396
            // Escape character, check the next character to make sure that
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
            // the literals match
            j++;
            if (j < length)
              {
                maskChar = mask.charAt(j);
                if (! isPlaceHolder && getValueContainsLiteralCharacters()
                    && valueChar != maskChar)
                  throw new ParseException ("Invalid character: "+ valueChar, i);
                if (getValueContainsLiteralCharacters())
                  {
                    result.append(maskChar);
                  }
                i++;
              }
            else if (! isPlaceHolder)
              throw new ParseException("Bad match at trailing escape: ", i);
413 414
            break;
          default:
415 416 417 418 419 420 421 422
            if (! isPlaceHolder && getValueContainsLiteralCharacters()
                && valueChar != maskChar)
              throw new ParseException ("Invalid character: "+ valueChar, i);
            if (getValueContainsLiteralCharacters())
              {
                result.append(maskChar);
              }
            i++;
423 424 425 426
          }
      }
    return result.toString();
  }
427

428 429
  /**
   * Returns a String representation of the Object value based on the mask.
430
   *
431 432 433 434
   * @param value the value to convert
   * @throws ParseException if value is invalid for this mask and valid/invalid
   * character sets
   */
435
  public String valueToString(Object value) throws ParseException
436
  {
437 438
    String string = value != null ? value.toString() : "";
    return convertValueToString(string);
439
  }
440

441 442 443 444 445 446 447 448
  /**
   * This method takes in a String and runs it through the mask to make
   * sure that it is valid.  If <code>convert</code> is true, it also
   * converts letters to upper/lowercase as required by the mask.
   * @param value the String to convert
   * @return the converted String
   * @throws ParseException if the given String isn't valid for the mask
   */
449 450
  private String convertValueToString(String value)
    throws ParseException
451
  {
452
    CPStringBuilder result = new CPStringBuilder();
453 454
    char valueChar;
    boolean isPlaceHolder;
455

456 457
    int length = mask.length();
    for (int i = 0, j = 0; j < length; j++)
458
      {
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
        char maskChar = mask.charAt(j);
        if (i < value.length())
          {
            isPlaceHolder = false;
            valueChar = value.charAt(i);
            if (maskChar != ESCAPE_CHAR && valueChar != maskChar)
              {
                if (invalidChars != null
                    && invalidChars.indexOf(valueChar) != -1)
                  throw new ParseException("Invalid character: " + valueChar,
                                           i);
                if (validChars != null && validChars.indexOf(valueChar) == -1)
                  throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar,
                                           i);
              }
          }
        else if (placeHolder != null && i < placeHolder.length())
          {
            isPlaceHolder = true;
            valueChar = placeHolder.charAt(i);
          }
        else
          {
            isPlaceHolder = true;
            valueChar = placeHolderChar;
          }

486
        // This switch block on the mask character checks that the character
487 488
        // within <code>value</code> at that point is valid according to the
        // mask and also converts to upper/lowercase as needed.
489
        switch (maskChar)
490 491
          {
          case NUM_CHAR:
492 493 494 495
            if ( ! isPlaceHolder && ! Character.isDigit(valueChar))
              throw new ParseException("Number expected: " + valueChar, i);
            result.append(valueChar);
            i++;
496 497
            break;
          case UPPERCASE_CHAR:
498
            if (! Character.isLetter(valueChar))
499
              throw new ParseException("Letter expected", i);
500 501
            result.append(Character.toUpperCase(valueChar));
            i++;
502 503
            break;
          case LOWERCASE_CHAR:
504
            if (! Character.isLetter(valueChar))
505
              throw new ParseException("Letter expected", i);
506 507
            result.append(Character.toLowerCase(valueChar));
            i++;
508 509
            break;
          case ALPHANUM_CHAR:
510
            if (! Character.isLetterOrDigit(valueChar))
511
              throw new ParseException("Letter or number expected", i);
512 513
            result.append(valueChar);
            i++;
514 515
            break;
          case LETTER_CHAR:
516
            if (! Character.isLetter(valueChar))
517
              throw new ParseException("Letter expected", i);
518 519
            result.append(valueChar);
            i++;
520 521
            break;
          case HEX_CHAR:
522
            if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
523
              throw new ParseException("Hexadecimal character expected", i);
524 525
            result.append(valueChar);
            i++;
526 527
            break;
          case ANYTHING_CHAR:
528 529
            result.append(valueChar);
            i++;
530 531
            break;
          case ESCAPE_CHAR:
532
            // Escape character, check the next character to make sure that
533 534
            // the literals match
            j++;
535 536 537 538 539 540 541 542 543 544
            if (j < length)
              {
                maskChar = mask.charAt(j);
                if (! isPlaceHolder && getValueContainsLiteralCharacters()
                    && valueChar != maskChar)
                  throw new ParseException ("Invalid character: "+ valueChar, i);
                if (getValueContainsLiteralCharacters())
                  i++;
                result.append(maskChar);
              }
545 546
            break;
          default:
547 548 549 550 551 552
            if (! isPlaceHolder && getValueContainsLiteralCharacters()
                && valueChar != maskChar)
              throw new ParseException ("Invalid character: "+ valueChar, i);
            if (getValueContainsLiteralCharacters())
              i++;
            result.append(maskChar);
553 554 555 556 557 558
          }
      }
    return result.toString();
  }

}