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