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