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