1 /*
   2  * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.text;
  27 
  28 import java.io.*;
  29 import java.text.*;
  30 import java.util.*;
  31 import javax.swing.*;
  32 
  33 /**
  34  * <code>MaskFormatter</code> is used to format and edit strings. The behavior
  35  * of a <code>MaskFormatter</code> is controlled by way of a String mask
  36  * that specifies the valid characters that can be contained at a particular
  37  * location in the <code>Document</code> model. The following characters can
  38  * be specified:
  39  *
  40  * <table class="striped">
  41  * <caption>Valid characters and their descriptions</caption>
  42  * <thead>
  43  *   <tr>
  44  *     <th scope="col">Character
  45  *     <th scope="col">Description
  46  * </thead>
  47  * <tbody>
  48  *   <tr>
  49  *     <th scope="row">#
  50  *     <td>Any valid number, uses {@code Character.isDigit}.
  51  *   <tr>
  52  *     <th scope="row">'
  53  *     <td>Escape character, used to escape any of the special formatting
  54  *     characters.
  55  *   <tr>
  56  *     <th scope="row">U
  57  *     <td>Any character ({@code Character.isLetter}). All lowercase letters are
  58  *     mapped to upper case.
  59  *   <tr>
  60  *     <th scope="row">L
  61  *     <td>Any character ({@code Character.isLetter}). All upper case letters
  62  *     are mapped to lower case.
  63  *   <tr>
  64  *     <th scope="row">A
  65  *     <td>Any character or number ({@code Character.isLetter} or
  66  *     {@code Character.isDigit}).
  67  *   <tr>
  68  *     <th scope="row">?
  69  *     <td>Any character ({@code Character.isLetter}).
  70  *   <tr>
  71  *     <th scope="row">*
  72  *     <td>Anything.
  73  *   <tr>
  74  *     <th scope="row">H
  75  *     <td>Any hex character (0-9, a-f or A-F).
  76  * </tbody>
  77  * </table>
  78  *
  79  * <p>
  80  * Typically characters correspond to one char, but in certain languages this
  81  * is not the case. The mask is on a per character basis, and will thus
  82  * adjust to fit as many chars as are needed.
  83  * <p>
  84  * You can further restrict the characters that can be input by the
  85  * <code>setInvalidCharacters</code> and <code>setValidCharacters</code>
  86  * methods. <code>setInvalidCharacters</code> allows you to specify
  87  * which characters are not legal. <code>setValidCharacters</code> allows
  88  * you to specify which characters are valid. For example, the following
  89  * code block is equivalent to a mask of '0xHHH' with no invalid/valid
  90  * characters:
  91  * <pre>
  92  * MaskFormatter formatter = new MaskFormatter("0x***");
  93  * formatter.setValidCharacters("0123456789abcdefABCDEF");
  94  * </pre>
  95  * <p>
  96  * When initially formatting a value if the length of the string is
  97  * less than the length of the mask, two things can happen. Either
  98  * the placeholder string will be used, or the placeholder character will
  99  * be used. Precedence is given to the placeholder string. For example:
 100  * <pre>
 101  *   MaskFormatter formatter = new MaskFormatter("###-####");
 102  *   formatter.setPlaceholderCharacter('_');
 103  *   formatter.getDisplayValue(tf, "123");
 104  * </pre>
 105  * <p>
 106  * Would result in the string '123-____'. If
 107  * <code>setPlaceholder("555-1212")</code> was invoked '123-1212' would
 108  * result. The placeholder String is only used on the initial format,
 109  * on subsequent formats only the placeholder character will be used.
 110  * <p>
 111  * If a <code>MaskFormatter</code> is configured to only allow valid characters
 112  * (<code>setAllowsInvalid(false)</code>) literal characters will be skipped as
 113  * necessary when editing. Consider a <code>MaskFormatter</code> with
 114  * the mask "###-####" and current value "555-1212". Using the right
 115  * arrow key to navigate through the field will result in (| indicates the
 116  * position of the caret):
 117  * <pre>
 118  *   |555-1212
 119  *   5|55-1212
 120  *   55|5-1212
 121  *   555-|1212
 122  *   555-1|212
 123  * </pre>
 124  * The '-' is a literal (non-editable) character, and is skipped.
 125  * <p>
 126  * Similar behavior will result when editing. Consider inserting the string
 127  * '123-45' and '12345' into the <code>MaskFormatter</code> in the
 128  * previous example. Both inserts will result in the same String,
 129  * '123-45__'. When <code>MaskFormatter</code>
 130  * is processing the insert at character position 3 (the '-'), two things can
 131  * happen:
 132  * <ol>
 133  *   <li>If the inserted character is '-', it is accepted.
 134  *   <li>If the inserted character matches the mask for the next non-literal
 135  *       character, it is accepted at the new location.
 136  *   <li>Anything else results in an invalid edit
 137  * </ol>
 138  * <p>
 139  * By default <code>MaskFormatter</code> will not allow invalid edits, you can
 140  * change this with the <code>setAllowsInvalid</code> method, and will
 141  * commit edits on valid edits (use the <code>setCommitsOnValidEdit</code> to
 142  * change this).
 143  * <p>
 144  * By default, <code>MaskFormatter</code> is in overwrite mode. That is as
 145  * characters are typed a new character is not inserted, rather the character
 146  * at the current location is replaced with the newly typed character. You
 147  * can change this behavior by way of the method <code>setOverwriteMode</code>.
 148  * <p>
 149  * <strong>Warning:</strong>
 150  * Serialized objects of this class will not be compatible with
 151  * future Swing releases. The current serialization support is
 152  * appropriate for short term storage or RMI between applications running
 153  * the same version of Swing.  As of 1.4, support for long term storage
 154  * of all JavaBeans
 155  * has been added to the <code>java.beans</code> package.
 156  * Please see {@link java.beans.XMLEncoder}.
 157  *
 158  * @since 1.4
 159  */
 160 @SuppressWarnings("serial") // Same-version serialization only
 161 public class MaskFormatter extends DefaultFormatter {
 162     // Potential values in mask.
 163     private static final char DIGIT_KEY = '#';
 164     private static final char LITERAL_KEY = '\'';
 165     private static final char UPPERCASE_KEY = 'U';
 166     private static final char LOWERCASE_KEY = 'L';
 167     private static final char ALPHA_NUMERIC_KEY = 'A';
 168     private static final char CHARACTER_KEY = '?';
 169     private static final char ANYTHING_KEY = '*';
 170     private static final char HEX_KEY = 'H';
 171 
 172     private static final MaskCharacter[] EmptyMaskChars = new MaskCharacter[0];
 173 
 174     /** The user specified mask. */
 175     private String mask;
 176 
 177     private transient MaskCharacter[] maskChars;
 178 
 179     /** List of valid characters. */
 180     private String validCharacters;
 181 
 182     /** List of invalid characters. */
 183     private String invalidCharacters;
 184 
 185     /** String used for the passed in value if it does not completely
 186      * fill the mask. */
 187     private String placeholderString;
 188 
 189     /** String used to represent characters not present. */
 190     private char placeholder;
 191 
 192     /** Indicates if the value contains the literal characters. */
 193     private boolean containsLiteralChars;
 194 
 195 
 196     /**
 197      * Creates a MaskFormatter with no mask.
 198      */
 199     public MaskFormatter() {
 200         setAllowsInvalid(false);
 201         containsLiteralChars = true;
 202         maskChars = EmptyMaskChars;
 203         placeholder = ' ';
 204     }
 205 
 206     /**
 207      * Creates a <code>MaskFormatter</code> with the specified mask.
 208      * A <code>ParseException</code>
 209      * will be thrown if <code>mask</code> is an invalid mask.
 210      * @param mask the mask
 211      * @throws ParseException if mask does not contain valid mask characters
 212      */
 213     public MaskFormatter(String mask) throws ParseException {
 214         this();
 215         setMask(mask);
 216     }
 217 
 218     /**
 219      * Sets the mask dictating the legal characters.
 220      * This will throw a <code>ParseException</code> if <code>mask</code> is
 221      * not valid.
 222      * @param mask the mask
 223      *
 224      * @throws ParseException if mask does not contain valid mask characters
 225      */
 226     public void setMask(String mask) throws ParseException {
 227         this.mask = mask;
 228         updateInternalMask();
 229     }
 230 
 231     /**
 232      * Returns the formatting mask.
 233      *
 234      * @return Mask dictating legal character values.
 235      */
 236     public String getMask() {
 237         return mask;
 238     }
 239 
 240     /**
 241      * Allows for further restricting of the characters that can be input.
 242      * Only characters specified in the mask, not in the
 243      * <code>invalidCharacters</code>, and in
 244      * <code>validCharacters</code> will be allowed to be input. Passing
 245      * in null (the default) implies the valid characters are only bound
 246      * by the mask and the invalid characters.
 247      *
 248      * @param validCharacters If non-null, specifies legal characters.
 249      */
 250     public void setValidCharacters(String validCharacters) {
 251         this.validCharacters = validCharacters;
 252     }
 253 
 254     /**
 255      * Returns the valid characters that can be input.
 256      *
 257      * @return Legal characters
 258      */
 259     public String getValidCharacters() {
 260         return validCharacters;
 261     }
 262 
 263     /**
 264      * Allows for further restricting of the characters that can be input.
 265      * Only characters specified in the mask, not in the
 266      * <code>invalidCharacters</code>, and in
 267      * <code>validCharacters</code> will be allowed to be input. Passing
 268      * in null (the default) implies the valid characters are only bound
 269      * by the mask and the valid characters.
 270      *
 271      * @param invalidCharacters If non-null, specifies illegal characters.
 272      */
 273     public void setInvalidCharacters(String invalidCharacters) {
 274         this.invalidCharacters = invalidCharacters;
 275     }
 276 
 277     /**
 278      * Returns the characters that are not valid for input.
 279      *
 280      * @return illegal characters.
 281      */
 282     public String getInvalidCharacters() {
 283         return invalidCharacters;
 284     }
 285 
 286     /**
 287      * Sets the string to use if the value does not completely fill in
 288      * the mask. A null value implies the placeholder char should be used.
 289      *
 290      * @param placeholder String used when formatting if the value does not
 291      *        completely fill the mask
 292      */
 293     public void setPlaceholder(String placeholder) {
 294         this.placeholderString = placeholder;
 295     }
 296 
 297     /**
 298      * Returns the String to use if the value does not completely fill
 299      * in the mask.
 300      *
 301      * @return String used when formatting if the value does not
 302      *        completely fill the mask
 303      */
 304     public String getPlaceholder() {
 305         return placeholderString;
 306     }
 307 
 308     /**
 309      * Sets the character to use in place of characters that are not present
 310      * in the value, ie the user must fill them in. The default value is
 311      * a space.
 312      * <p>
 313      * This is only applicable if the placeholder string has not been
 314      * specified, or does not completely fill in the mask.
 315      *
 316      * @param placeholder Character used when formatting if the value does not
 317      *        completely fill the mask
 318      */
 319     public void setPlaceholderCharacter(char placeholder) {
 320         this.placeholder = placeholder;
 321     }
 322 
 323     /**
 324      * Returns the character to use in place of characters that are not present
 325      * in the value, ie the user must fill them in.
 326      *
 327      * @return Character used when formatting if the value does not
 328      *        completely fill the mask
 329      */
 330     public char getPlaceholderCharacter() {
 331         return placeholder;
 332     }
 333 
 334     /**
 335      * If true, the returned value and set value will also contain the literal
 336      * characters in mask.
 337      * <p>
 338      * For example, if the mask is <code>'(###) ###-####'</code>, the
 339      * current value is <code>'(415) 555-1212'</code>, and
 340      * <code>valueContainsLiteralCharacters</code> is
 341      * true <code>stringToValue</code> will return
 342      * <code>'(415) 555-1212'</code>. On the other hand, if
 343      * <code>valueContainsLiteralCharacters</code> is false,
 344      * <code>stringToValue</code> will return <code>'4155551212'</code>.
 345      *
 346      * @param containsLiteralChars Used to indicate if literal characters in
 347      *        mask should be returned in stringToValue
 348      */
 349     public void setValueContainsLiteralCharacters(
 350                         boolean containsLiteralChars) {
 351         this.containsLiteralChars = containsLiteralChars;
 352     }
 353 
 354     /**
 355      * Returns true if <code>stringToValue</code> should return literal
 356      * characters in the mask.
 357      *
 358      * @return True if literal characters in mask should be returned in
 359      *         stringToValue
 360      */
 361     public boolean getValueContainsLiteralCharacters() {
 362         return containsLiteralChars;
 363     }
 364 
 365     /**
 366      * Parses the text, returning the appropriate Object representation of
 367      * the String <code>value</code>. This strips the literal characters as
 368      * necessary and invokes supers <code>stringToValue</code>, so that if
 369      * you have specified a value class (<code>setValueClass</code>) an
 370      * instance of it will be created. This will throw a
 371      * <code>ParseException</code> if the value does not match the current
 372      * mask.  Refer to {@link #setValueContainsLiteralCharacters} for details
 373      * on how literals are treated.
 374      *
 375      * @throws ParseException if there is an error in the conversion
 376      * @param value String to convert
 377      * @see #setValueContainsLiteralCharacters
 378      * @return Object representation of text
 379      */
 380     public Object stringToValue(String value) throws ParseException {
 381         return stringToValue(value, true);
 382     }
 383 
 384     /**
 385      * Returns a String representation of the Object <code>value</code>
 386      * based on the mask.  Refer to
 387      * {@link #setValueContainsLiteralCharacters} for details
 388      * on how literals are treated.
 389      *
 390      * @throws ParseException if there is an error in the conversion
 391      * @param value Value to convert
 392      * @see #setValueContainsLiteralCharacters
 393      * @return String representation of value
 394      */
 395     public String valueToString(Object value) throws ParseException {
 396         String sValue = (value == null) ? "" : value.toString();
 397         StringBuilder result = new StringBuilder();
 398         String placeholder = getPlaceholder();
 399         int[] valueCounter = { 0 };
 400 
 401         append(result, sValue, valueCounter, placeholder, maskChars);
 402         return result.toString();
 403     }
 404 
 405     /**
 406      * Installs the <code>DefaultFormatter</code> onto a particular
 407      * <code>JFormattedTextField</code>.
 408      * This will invoke <code>valueToString</code> to convert the
 409      * current value from the <code>JFormattedTextField</code> to
 410      * a String. This will then install the <code>Action</code>s from
 411      * <code>getActions</code>, the <code>DocumentFilter</code>
 412      * returned from <code>getDocumentFilter</code> and the
 413      * <code>NavigationFilter</code> returned from
 414      * <code>getNavigationFilter</code> onto the
 415      * <code>JFormattedTextField</code>.
 416      * <p>
 417      * Subclasses will typically only need to override this if they
 418      * wish to install additional listeners on the
 419      * <code>JFormattedTextField</code>.
 420      * <p>
 421      * If there is a <code>ParseException</code> in converting the
 422      * current value to a String, this will set the text to an empty
 423      * String, and mark the <code>JFormattedTextField</code> as being
 424      * in an invalid state.
 425      * <p>
 426      * While this is a public method, this is typically only useful
 427      * for subclassers of <code>JFormattedTextField</code>.
 428      * <code>JFormattedTextField</code> will invoke this method at
 429      * the appropriate times when the value changes, or its internal
 430      * state changes.
 431      *
 432      * @param ftf JFormattedTextField to format for, may be null indicating
 433      *            uninstall from current JFormattedTextField.
 434      */
 435     public void install(JFormattedTextField ftf) {
 436         super.install(ftf);
 437         // valueToString doesn't throw, but stringToValue does, need to
 438         // update the editValid state appropriately
 439         if (ftf != null) {
 440             Object value = ftf.getValue();
 441 
 442             try {
 443                 stringToValue(valueToString(value));
 444             } catch (ParseException pe) {
 445                 setEditValid(false);
 446             }
 447         }
 448     }
 449 
 450     /**
 451      * Actual <code>stringToValue</code> implementation.
 452      * If <code>completeMatch</code> is true, the value must exactly match
 453      * the mask, on the other hand if <code>completeMatch</code> is false
 454      * the string must match the mask or the placeholder string.
 455      */
 456     private Object stringToValue(String value, boolean completeMatch) throws
 457                          ParseException {
 458         int errorOffset;
 459 
 460         if ((errorOffset = getInvalidOffset(value, completeMatch)) == -1) {
 461             if (!getValueContainsLiteralCharacters()) {
 462                 value = stripLiteralChars(value);
 463             }
 464             return super.stringToValue(value);
 465         }
 466         throw new ParseException("stringToValue passed invalid value",
 467                                  errorOffset);
 468     }
 469 
 470     /**
 471      * Returns -1 if the passed in string is valid, otherwise the index of
 472      * the first bogus character is returned.
 473      */
 474     private int getInvalidOffset(String string, boolean completeMatch) {
 475         int iLength = string.length();
 476 
 477         if (iLength != getMaxLength()) {
 478             // trivially false
 479             return iLength;
 480         }
 481         for (int counter = 0, max = string.length(); counter < max; counter++){
 482             char aChar = string.charAt(counter);
 483 
 484             if (!isValidCharacter(counter, aChar) &&
 485                 (completeMatch || !isPlaceholder(counter, aChar))) {
 486                 return counter;
 487             }
 488         }
 489         return -1;
 490     }
 491 
 492     /**
 493      * Invokes <code>append</code> on the mask characters in
 494      * <code>mask</code>.
 495      */
 496     private void append(StringBuilder result, String value, int[] index,
 497                         String placeholder, MaskCharacter[] mask)
 498                           throws ParseException {
 499         for (int counter = 0, maxCounter = mask.length;
 500              counter < maxCounter; counter++) {
 501             mask[counter].append(result, value, index, placeholder);
 502         }
 503     }
 504 
 505     /**
 506      * Updates the internal representation of the mask.
 507      */
 508     private void updateInternalMask() throws ParseException {
 509         String mask = getMask();
 510         ArrayList<MaskCharacter> fixed = new ArrayList<MaskCharacter>();
 511         ArrayList<MaskCharacter> temp = fixed;
 512 
 513         if (mask != null) {
 514             for (int counter = 0, maxCounter = mask.length();
 515                  counter < maxCounter; counter++) {
 516                 char maskChar = mask.charAt(counter);
 517 
 518                 switch (maskChar) {
 519                 case DIGIT_KEY:
 520                     temp.add(new DigitMaskCharacter());
 521                     break;
 522                 case LITERAL_KEY:
 523                     if (++counter < maxCounter) {
 524                         maskChar = mask.charAt(counter);
 525                         temp.add(new LiteralCharacter(maskChar));
 526                     }
 527                     // else: Could actually throw if else
 528                     break;
 529                 case UPPERCASE_KEY:
 530                     temp.add(new UpperCaseCharacter());
 531                     break;
 532                 case LOWERCASE_KEY:
 533                     temp.add(new LowerCaseCharacter());
 534                     break;
 535                 case ALPHA_NUMERIC_KEY:
 536                     temp.add(new AlphaNumericCharacter());
 537                     break;
 538                 case CHARACTER_KEY:
 539                     temp.add(new CharCharacter());
 540                     break;
 541                 case ANYTHING_KEY:
 542                     temp.add(new MaskCharacter());
 543                     break;
 544                 case HEX_KEY:
 545                     temp.add(new HexCharacter());
 546                     break;
 547                 default:
 548                     temp.add(new LiteralCharacter(maskChar));
 549                     break;
 550                 }
 551             }
 552         }
 553         if (fixed.size() == 0) {
 554             maskChars = EmptyMaskChars;
 555         }
 556         else {
 557             maskChars = new MaskCharacter[fixed.size()];
 558             fixed.toArray(maskChars);
 559         }
 560     }
 561 
 562     /**
 563      * Returns the MaskCharacter at the specified location.
 564      */
 565     private MaskCharacter getMaskCharacter(int index) {
 566         if (index >= maskChars.length) {
 567             return null;
 568         }
 569         return maskChars[index];
 570     }
 571 
 572     /**
 573      * Returns true if the placeholder character matches aChar.
 574      */
 575     private boolean isPlaceholder(int index, char aChar) {
 576         return (getPlaceholderCharacter() == aChar);
 577     }
 578 
 579     /**
 580      * Returns true if the passed in character matches the mask at the
 581      * specified location.
 582      */
 583     private boolean isValidCharacter(int index, char aChar) {
 584         return getMaskCharacter(index).isValidCharacter(aChar);
 585     }
 586 
 587     /**
 588      * Returns true if the character at the specified location is a literal,
 589      * that is it can not be edited.
 590      */
 591     private boolean isLiteral(int index) {
 592         return getMaskCharacter(index).isLiteral();
 593     }
 594 
 595     /**
 596      * Returns the maximum length the text can be.
 597      */
 598     private int getMaxLength() {
 599         return maskChars.length;
 600     }
 601 
 602     /**
 603      * Returns the literal character at the specified location.
 604      */
 605     private char getLiteral(int index) {
 606         return getMaskCharacter(index).getChar((char)0);
 607     }
 608 
 609     /**
 610      * Returns the character to insert at the specified location based on
 611      * the passed in character.  This provides a way to map certain sets
 612      * of characters to alternative values (lowercase to
 613      * uppercase...).
 614      */
 615     private char getCharacter(int index, char aChar) {
 616         return getMaskCharacter(index).getChar(aChar);
 617     }
 618 
 619     /**
 620      * Removes the literal characters from the passed in string.
 621      */
 622     private String stripLiteralChars(String string) {
 623         StringBuilder sb = null;
 624         int last = 0;
 625 
 626         for (int counter = 0, max = string.length(); counter < max; counter++){
 627             if (isLiteral(counter)) {
 628                 if (sb == null) {
 629                     sb = new StringBuilder();
 630                     if (counter > 0) {
 631                         sb.append(string.substring(0, counter));
 632                     }
 633                     last = counter + 1;
 634                 }
 635                 else if (last != counter) {
 636                     sb.append(string.substring(last, counter));
 637                 }
 638                 last = counter + 1;
 639             }
 640         }
 641         if (sb == null) {
 642             // Assume the mask isn't all literals.
 643             return string;
 644         }
 645         else if (last != string.length()) {
 646             if (sb == null) {
 647                 return string.substring(last);
 648             }
 649             sb.append(string.substring(last));
 650         }
 651         return sb.toString();
 652     }
 653 
 654 
 655     /**
 656      * Subclassed to update the internal representation of the mask after
 657      * the default read operation has completed.
 658      */
 659     private void readObject(ObjectInputStream s)
 660         throws IOException, ClassNotFoundException {
 661         ObjectInputStream.GetField f = s.readFields();
 662 
 663         validCharacters = (String) f.get("validCharacters", null);
 664         invalidCharacters = (String) f.get("invalidCharacters", null);
 665         placeholderString = (String) f.get("placeholderString", null);
 666         placeholder = f.get("placeholder", '\0');
 667         containsLiteralChars = f.get("containsLiteralChars", false);
 668         mask = (String) f.get("mask", null);
 669 
 670         try {
 671             updateInternalMask();
 672         } catch (ParseException pe) {
 673             // assert();
 674         }
 675     }
 676 
 677     /**
 678      * Returns true if the MaskFormatter allows invalid, or
 679      * the offset is less than the max length and the character at
 680      * <code>offset</code> is a literal.
 681      */
 682     boolean isNavigatable(int offset) {
 683         if (!getAllowsInvalid()) {
 684             return (offset < getMaxLength() && !isLiteral(offset));
 685         }
 686         return true;
 687     }
 688 
 689     /*
 690      * Returns true if the operation described by <code>rh</code> will
 691      * result in a legal edit.  This may set the <code>value</code>
 692      * field of <code>rh</code>.
 693      * <p>
 694      * This is overriden to return true for a partial match.
 695      */
 696     boolean isValidEdit(ReplaceHolder rh) {
 697         if (!getAllowsInvalid()) {
 698             String newString = getReplaceString(rh.offset, rh.length, rh.text);
 699 
 700             try {
 701                 rh.value = stringToValue(newString, false);
 702 
 703                 return true;
 704             } catch (ParseException pe) {
 705                 return false;
 706             }
 707         }
 708         return true;
 709     }
 710 
 711     /**
 712      * This method does the following (assuming !getAllowsInvalid()):
 713      * iterate over the max of the deleted region or the text length, for
 714      * each character:
 715      * <ol>
 716      * <li>If it is valid (matches the mask at the particular position, or
 717      *     matches the literal character at the position), allow it
 718      * <li>Else if the position identifies a literal character, add it. This
 719      *     allows for the user to paste in text that may/may not contain
 720      *     the literals.  For example, in pasing in 5551212 into ###-####
 721      *     when the 1 is evaluated it is illegal (by the first test), but there
 722      *     is a literal at this position (-), so it is used.  NOTE: This has
 723      *     a problem that you can't tell (without looking ahead) if you should
 724      *     eat literals in the text. For example, if you paste '555' into
 725      *     #5##, should it result in '5555' or '555 '? The current code will
 726      *     result in the latter, which feels a little better as selecting
 727      *     text than pasting will always result in the same thing.
 728      * <li>Else if at the end of the inserted text, the replace the item with
 729      *     the placeholder
 730      * <li>Otherwise the insert is bogus and false is returned.
 731      * </ol>
 732      */
 733     boolean canReplace(ReplaceHolder rh) {
 734         // This method is rather long, but much of the burden is in
 735         // maintaining a String and swapping to a StringBuilder only if
 736         // absolutely necessary.
 737         if (!getAllowsInvalid()) {
 738             StringBuilder replace = null;
 739             String text = rh.text;
 740             int tl = (text != null) ? text.length() : 0;
 741 
 742             if (tl == 0 && rh.length == 1 && getFormattedTextField().
 743                               getSelectionStart() != rh.offset) {
 744                 // Backspace, adjust to actually delete next non-literal.
 745                 while (rh.offset > 0 && isLiteral(rh.offset)) {
 746                     rh.offset--;
 747                 }
 748             }
 749             int max = Math.min(getMaxLength() - rh.offset,
 750                                Math.max(tl, rh.length));
 751             for (int counter = 0, textIndex = 0; counter < max; counter++) {
 752                 if (textIndex < tl && isValidCharacter(rh.offset + counter,
 753                                                    text.charAt(textIndex))) {
 754                     char aChar = text.charAt(textIndex);
 755                     if (aChar != getCharacter(rh.offset + counter, aChar)) {
 756                         if (replace == null) {
 757                             replace = new StringBuilder();
 758                             if (textIndex > 0) {
 759                                 replace.append(text.substring(0, textIndex));
 760                             }
 761                         }
 762                     }
 763                     if (replace != null) {
 764                         replace.append(getCharacter(rh.offset + counter,
 765                                                     aChar));
 766                     }
 767                     textIndex++;
 768                 }
 769                 else if (isLiteral(rh.offset + counter)) {
 770                     if (replace != null) {
 771                         replace.append(getLiteral(rh.offset + counter));
 772                         if (textIndex < tl) {
 773                             max = Math.min(max + 1, getMaxLength() -
 774                                            rh.offset);
 775                         }
 776                     }
 777                     else if (textIndex > 0) {
 778                         replace = new StringBuilder(max);
 779                         replace.append(text.substring(0, textIndex));
 780                         replace.append(getLiteral(rh.offset + counter));
 781                         if (textIndex < tl) {
 782                             // Evaluate the character in text again.
 783                             max = Math.min(max + 1, getMaxLength() -
 784                                            rh.offset);
 785                         }
 786                         else if (rh.cursorPosition == -1) {
 787                             rh.cursorPosition = rh.offset + counter;
 788                         }
 789                     }
 790                     else {
 791                         rh.offset++;
 792                         rh.length--;
 793                         counter--;
 794                         max--;
 795                     }
 796                 }
 797                 else if (textIndex >= tl) {
 798                     // placeholder
 799                     if (replace == null) {
 800                         replace = new StringBuilder();
 801                         if (text != null) {
 802                             replace.append(text);
 803                         }
 804                     }
 805                     replace.append(getPlaceholderCharacter());
 806                     if (tl > 0 && rh.cursorPosition == -1) {
 807                         rh.cursorPosition = rh.offset + counter;
 808                     }
 809                 }
 810                 else {
 811                     // Bogus character.
 812                     return false;
 813                 }
 814             }
 815             if (replace != null) {
 816                 rh.text = replace.toString();
 817             }
 818             else if (text != null && rh.offset + tl > getMaxLength()) {
 819                 rh.text = text.substring(0, getMaxLength() - rh.offset);
 820             }
 821             if (getOverwriteMode() && rh.text != null) {
 822                 rh.length = rh.text.length();
 823             }
 824         }
 825         return super.canReplace(rh);
 826     }
 827 
 828 
 829     //
 830     // Interal classes used to represent the mask.
 831     //
 832     private class MaskCharacter {
 833         /**
 834          * Subclasses should override this returning true if the instance
 835          * represents a literal character. The default implementation
 836          * returns false.
 837          */
 838         public boolean isLiteral() {
 839             return false;
 840         }
 841 
 842         /**
 843          * Returns true if <code>aChar</code> is a valid reprensentation of
 844          * the receiver. The default implementation returns true if the
 845          * receiver represents a literal character and <code>getChar</code>
 846          * == aChar. Otherwise, this will return true is <code>aChar</code>
 847          * is contained in the valid characters and not contained
 848          * in the invalid characters.
 849          */
 850         public boolean isValidCharacter(char aChar) {
 851             if (isLiteral()) {
 852                 return (getChar(aChar) == aChar);
 853             }
 854 
 855             aChar = getChar(aChar);
 856 
 857             String filter = getValidCharacters();
 858 
 859             if (filter != null && filter.indexOf(aChar) == -1) {
 860                 return false;
 861             }
 862             filter = getInvalidCharacters();
 863             if (filter != null && filter.indexOf(aChar) != -1) {
 864                 return false;
 865             }
 866             return true;
 867         }
 868 
 869         /**
 870          * Returns the character to insert for <code>aChar</code>. The
 871          * default implementation returns <code>aChar</code>. Subclasses
 872          * that wish to do some sort of mapping, perhaps lower case to upper
 873          * case should override this and do the necessary mapping.
 874          */
 875         public char getChar(char aChar) {
 876             return aChar;
 877         }
 878 
 879         /**
 880          * Appends the necessary character in <code>formatting</code> at
 881          * <code>index</code> to <code>buff</code>.
 882          */
 883         public void append(StringBuilder buff, String formatting, int[] index,
 884                            String placeholder)
 885                           throws ParseException {
 886             boolean inString = index[0] < formatting.length();
 887             char aChar = inString ? formatting.charAt(index[0]) : 0;
 888 
 889             if (isLiteral()) {
 890                 buff.append(getChar(aChar));
 891                 if (getValueContainsLiteralCharacters()) {
 892                     if (inString && aChar != getChar(aChar)) {
 893                         throw new ParseException("Invalid character: " +
 894                                                  aChar, index[0]);
 895                     }
 896                     index[0] = index[0] + 1;
 897                 }
 898             }
 899             else if (index[0] >= formatting.length()) {
 900                 if (placeholder != null && index[0] < placeholder.length()) {
 901                     buff.append(placeholder.charAt(index[0]));
 902                 }
 903                 else {
 904                     buff.append(getPlaceholderCharacter());
 905                 }
 906                 index[0] = index[0] + 1;
 907             }
 908             else if (isValidCharacter(aChar)) {
 909                 buff.append(getChar(aChar));
 910                 index[0] = index[0] + 1;
 911             }
 912             else {
 913                 throw new ParseException("Invalid character: " + aChar,
 914                                          index[0]);
 915             }
 916         }
 917     }
 918 
 919 
 920     /**
 921      * Used to represent a fixed character in the mask.
 922      */
 923     private class LiteralCharacter extends MaskCharacter {
 924         private char fixedChar;
 925 
 926         public LiteralCharacter(char fixedChar) {
 927             this.fixedChar = fixedChar;
 928         }
 929 
 930         public boolean isLiteral() {
 931             return true;
 932         }
 933 
 934         public char getChar(char aChar) {
 935             return fixedChar;
 936         }
 937     }
 938 
 939 
 940     /**
 941      * Represents a number, uses <code>Character.isDigit</code>.
 942      */
 943     private class DigitMaskCharacter extends MaskCharacter {
 944         public boolean isValidCharacter(char aChar) {
 945             return (Character.isDigit(aChar) &&
 946                     super.isValidCharacter(aChar));
 947         }
 948     }
 949 
 950 
 951     /**
 952      * Represents a character, lower case letters are mapped to upper case
 953      * using <code>Character.toUpperCase</code>.
 954      */
 955     private class UpperCaseCharacter extends MaskCharacter {
 956         public boolean isValidCharacter(char aChar) {
 957             return (Character.isLetter(aChar) &&
 958                      super.isValidCharacter(aChar));
 959         }
 960 
 961         public char getChar(char aChar) {
 962             return Character.toUpperCase(aChar);
 963         }
 964     }
 965 
 966 
 967     /**
 968      * Represents a character, upper case letters are mapped to lower case
 969      * using <code>Character.toLowerCase</code>.
 970      */
 971     private class LowerCaseCharacter extends MaskCharacter {
 972         public boolean isValidCharacter(char aChar) {
 973             return (Character.isLetter(aChar) &&
 974                      super.isValidCharacter(aChar));
 975         }
 976 
 977         public char getChar(char aChar) {
 978             return Character.toLowerCase(aChar);
 979         }
 980     }
 981 
 982 
 983     /**
 984      * Represents either a character or digit, uses
 985      * <code>Character.isLetterOrDigit</code>.
 986      */
 987     private class AlphaNumericCharacter extends MaskCharacter {
 988         public boolean isValidCharacter(char aChar) {
 989             return (Character.isLetterOrDigit(aChar) &&
 990                      super.isValidCharacter(aChar));
 991         }
 992     }
 993 
 994 
 995     /**
 996      * Represents a letter, uses <code>Character.isLetter</code>.
 997      */
 998     private class CharCharacter extends MaskCharacter {
 999         public boolean isValidCharacter(char aChar) {
1000             return (Character.isLetter(aChar) &&
1001                      super.isValidCharacter(aChar));
1002         }
1003     }
1004 
1005 
1006     /**
1007      * Represents a hex character, 0-9a-fA-F. a-f is mapped to A-F
1008      */
1009     private class HexCharacter extends MaskCharacter {
1010         public boolean isValidCharacter(char aChar) {
1011             return ((aChar == '0' || aChar == '1' ||
1012                      aChar == '2' || aChar == '3' ||
1013                      aChar == '4' || aChar == '5' ||
1014                      aChar == '6' || aChar == '7' ||
1015                      aChar == '8' || aChar == '9' ||
1016                      aChar == 'a' || aChar == 'A' ||
1017                      aChar == 'b' || aChar == 'B' ||
1018                      aChar == 'c' || aChar == 'C' ||
1019                      aChar == 'd' || aChar == 'D' ||
1020                      aChar == 'e' || aChar == 'E' ||
1021                      aChar == 'f' || aChar == 'F') &&
1022                     super.isValidCharacter(aChar));
1023         }
1024 
1025         public char getChar(char aChar) {
1026             if (Character.isDigit(aChar)) {
1027                 return aChar;
1028             }
1029             return Character.toUpperCase(aChar);
1030         }
1031     }
1032 }