Home » openjdk-7 » javax » swing » text » [javadoc | source]

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

Home » openjdk-7 » javax » swing » text » [javadoc | source]