1 /*
   2  * Copyright (c) 2000, 2008, 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 package javax.swing.text;
  26 
  27 import java.awt.event.ActionEvent;
  28 import java.io.*;
  29 import java.text.*;
  30 import java.text.AttributedCharacterIterator.Attribute;
  31 import java.util.*;
  32 import javax.swing.*;
  33 
  34 /**
  35  * <code>InternationalFormatter</code> extends <code>DefaultFormatter</code>,
  36  * using an instance of <code>java.text.Format</code> to handle the
  37  * conversion to a String, and the conversion from a String.
  38  * <p>
  39  * If <code>getAllowsInvalid()</code> is false, this will ask the
  40  * <code>Format</code> to format the current text on every edit.
  41  * <p>
  42  * You can specify a minimum and maximum value by way of the
  43  * <code>setMinimum</code> and <code>setMaximum</code> methods. In order
  44  * for this to work the values returned from <code>stringToValue</code> must be
  45  * comparable to the min/max values by way of the <code>Comparable</code>
  46  * interface.
  47  * <p>
  48  * Be careful how you configure the <code>Format</code> and the
  49  * <code>InternationalFormatter</code>, as it is possible to create a
  50  * situation where certain values can not be input. Consider the date
  51  * format 'M/d/yy', an <code>InternationalFormatter</code> that is always
  52  * valid (<code>setAllowsInvalid(false)</code>), is in overwrite mode
  53  * (<code>setOverwriteMode(true)</code>) and the date 7/1/99. In this
  54  * case the user will not be able to enter a two digit month or day of
  55  * month. To avoid this, the format should be 'MM/dd/yy'.
  56  * <p>
  57  * If <code>InternationalFormatter</code> is configured to only allow valid
  58  * values (<code>setAllowsInvalid(false)</code>), every valid edit will result
  59  * in the text of the <code>JFormattedTextField</code> being completely reset
  60  * from the <code>Format</code>.
  61  * The cursor position will also be adjusted as literal characters are
  62  * added/removed from the resulting String.
  63  * <p>
  64  * <code>InternationalFormatter</code>'s behavior of
  65  * <code>stringToValue</code> is  slightly different than that of
  66  * <code>DefaultTextFormatter</code>, it does the following:
  67  * <ol>
  68  *   <li><code>parseObject</code> is invoked on the <code>Format</code>
  69  *       specified by <code>setFormat</code>
  70  *   <li>If a Class has been set for the values (<code>setValueClass</code>),
  71  *       supers implementation is invoked to convert the value returned
  72  *       from <code>parseObject</code> to the appropriate class.
  73  *   <li>If a <code>ParseException</code> has not been thrown, and the value
  74  *       is outside the min/max a <code>ParseException</code> is thrown.
  75  *   <li>The value is returned.
  76  * </ol>
  77  * <code>InternationalFormatter</code> implements <code>stringToValue</code>
  78  * in this manner so that you can specify an alternate Class than
  79  * <code>Format</code> may return.
  80  * <p>
  81  * <strong>Warning:</strong>
  82  * Serialized objects of this class will not be compatible with
  83  * future Swing releases. The current serialization support is
  84  * appropriate for short term storage or RMI between applications running
  85  * the same version of Swing.  As of 1.4, support for long term storage
  86  * of all JavaBeans<sup><font size="-2">TM</font></sup>
  87  * has been added to the <code>java.beans</code> package.
  88  * Please see {@link java.beans.XMLEncoder}.
  89  *
  90  * @see java.text.Format
  91  * @see java.lang.Comparable
  92  *
  93  * @since 1.4
  94  */
  95 public class InternationalFormatter extends DefaultFormatter {
  96     /**
  97      * Used by <code>getFields</code>.
  98      */
  99     private static final Format.Field[] EMPTY_FIELD_ARRAY =new Format.Field[0];
 100 
 101     /**
 102      * Object used to handle the conversion.
 103      */
 104     private Format format;
 105     /**
 106      * Can be used to impose a maximum value.
 107      */
 108     private Comparable max;
 109     /**
 110      * Can be used to impose a minimum value.
 111      */
 112     private Comparable min;
 113 
 114     /**
 115      * <code>InternationalFormatter</code>'s behavior is dicatated by a
 116      * <code>AttributedCharacterIterator</code> that is obtained from
 117      * the <code>Format</code>. On every edit, assuming
 118      * allows invalid is false, the <code>Format</code> instance is invoked
 119      * with <code>formatToCharacterIterator</code>. A <code>BitSet</code> is
 120      * also kept upto date with the non-literal characters, that is
 121      * for every index in the <code>AttributedCharacterIterator</code> an
 122      * entry in the bit set is updated based on the return value from
 123      * <code>isLiteral(Map)</code>. <code>isLiteral(int)</code> then uses
 124      * this cached information.
 125      * <p>
 126      * If allowsInvalid is false, every edit results in resetting the complete
 127      * text of the JTextComponent.
 128      * <p>
 129      * InternationalFormatterFilter can also provide two actions suitable for
 130      * incrementing and decrementing. To enable this a subclass must
 131      * override <code>getSupportsIncrement</code> to return true, and
 132      * override <code>adjustValue</code> to handle the changing of the
 133      * value. If you want to support changing the value outside of
 134      * the valid FieldPositions, you will need to override
 135      * <code>canIncrement</code>.
 136      */
 137     /**
 138      * A bit is set for every index identified in the
 139      * AttributedCharacterIterator that is not considered decoration.
 140      * This should only be used if validMask is true.
 141      */
 142     private transient BitSet literalMask;
 143     /**
 144      * Used to iterate over characters.
 145      */
 146     private transient AttributedCharacterIterator iterator;
 147     /**
 148      * True if the Format was able to convert the value to a String and
 149      * back.
 150      */
 151     private transient boolean validMask;
 152     /**
 153      * Current value being displayed.
 154      */
 155     private transient String string;
 156     /**
 157      * If true, DocumentFilter methods are unconditionally allowed,
 158      * and no checking is done on their values. This is used when
 159      * incrementing/decrementing via the actions.
 160      */
 161     private transient boolean ignoreDocumentMutate;
 162 
 163 
 164     /**
 165      * Creates an <code>InternationalFormatter</code> with no
 166      * <code>Format</code> specified.
 167      */
 168     public InternationalFormatter() {
 169         setOverwriteMode(false);
 170     }
 171 
 172     /**
 173      * Creates an <code>InternationalFormatter</code> with the specified
 174      * <code>Format</code> instance.
 175      *
 176      * @param format Format instance used for converting from/to Strings
 177      */
 178     public InternationalFormatter(Format format) {
 179         this();
 180         setFormat(format);
 181     }
 182 
 183     /**
 184      * Sets the format that dictates the legal values that can be edited
 185      * and displayed.
 186      *
 187      * @param format <code>Format</code> instance used for converting
 188      * from/to Strings
 189      */
 190     public void setFormat(Format format) {
 191         this.format = format;
 192     }
 193 
 194     /**
 195      * Returns the format that dictates the legal values that can be edited
 196      * and displayed.
 197      *
 198      * @return Format instance used for converting from/to Strings
 199      */
 200     public Format getFormat() {
 201         return format;
 202     }
 203 
 204     /**
 205      * Sets the minimum permissible value. If the <code>valueClass</code> has
 206      * not been specified, and <code>minimum</code> is non null, the
 207      * <code>valueClass</code> will be set to that of the class of
 208      * <code>minimum</code>.
 209      *
 210      * @param minimum Minimum legal value that can be input
 211      * @see #setValueClass
 212      */
 213     public void setMinimum(Comparable minimum) {
 214         if (getValueClass() == null && minimum != null) {
 215             setValueClass(minimum.getClass());
 216         }
 217         min = minimum;
 218     }
 219 
 220     /**
 221      * Returns the minimum permissible value.
 222      *
 223      * @return Minimum legal value that can be input
 224      */
 225     public Comparable getMinimum() {
 226         return min;
 227     }
 228 
 229     /**
 230      * Sets the maximum permissible value. If the <code>valueClass</code> has
 231      * not been specified, and <code>max</code> is non null, the
 232      * <code>valueClass</code> will be set to that of the class of
 233      * <code>max</code>.
 234      *
 235      * @param max Maximum legal value that can be input
 236      * @see #setValueClass
 237      */
 238     public void setMaximum(Comparable max) {
 239         if (getValueClass() == null && max != null) {
 240             setValueClass(max.getClass());
 241         }
 242         this.max = max;
 243     }
 244 
 245     /**
 246      * Returns the maximum permissible value.
 247      *
 248      * @return Maximum legal value that can be input
 249      */
 250     public Comparable getMaximum() {
 251         return max;
 252     }
 253 
 254     /**
 255      * Installs the <code>DefaultFormatter</code> onto a particular
 256      * <code>JFormattedTextField</code>.
 257      * This will invoke <code>valueToString</code> to convert the
 258      * current value from the <code>JFormattedTextField</code> to
 259      * a String. This will then install the <code>Action</code>s from
 260      * <code>getActions</code>, the <code>DocumentFilter</code>
 261      * returned from <code>getDocumentFilter</code> and the
 262      * <code>NavigationFilter</code> returned from
 263      * <code>getNavigationFilter</code> onto the
 264      * <code>JFormattedTextField</code>.
 265      * <p>
 266      * Subclasses will typically only need to override this if they
 267      * wish to install additional listeners on the
 268      * <code>JFormattedTextField</code>.
 269      * <p>
 270      * If there is a <code>ParseException</code> in converting the
 271      * current value to a String, this will set the text to an empty
 272      * String, and mark the <code>JFormattedTextField</code> as being
 273      * in an invalid state.
 274      * <p>
 275      * While this is a public method, this is typically only useful
 276      * for subclassers of <code>JFormattedTextField</code>.
 277      * <code>JFormattedTextField</code> will invoke this method at
 278      * the appropriate times when the value changes, or its internal
 279      * state changes.
 280      *
 281      * @param ftf JFormattedTextField to format for, may be null indicating
 282      *            uninstall from current JFormattedTextField.
 283      */
 284     public void install(JFormattedTextField ftf) {
 285         super.install(ftf);
 286         updateMaskIfNecessary();
 287         // invoked again as the mask should now be valid.
 288         positionCursorAtInitialLocation();
 289     }
 290 
 291     /**
 292      * Returns a String representation of the Object <code>value</code>.
 293      * This invokes <code>format</code> on the current <code>Format</code>.
 294      *
 295      * @throws ParseException if there is an error in the conversion
 296      * @param value Value to convert
 297      * @return String representation of value
 298      */
 299     public String valueToString(Object value) throws ParseException {
 300         if (value == null) {
 301             return "";
 302         }
 303         Format f = getFormat();
 304 
 305         if (f == null) {
 306             return value.toString();
 307         }
 308         return f.format(value);
 309     }
 310 
 311     /**
 312      * Returns the <code>Object</code> representation of the
 313      * <code>String</code> <code>text</code>.
 314      *
 315      * @param text <code>String</code> to convert
 316      * @return <code>Object</code> representation of text
 317      * @throws ParseException if there is an error in the conversion
 318      */
 319     public Object stringToValue(String text) throws ParseException {
 320         Object value = stringToValue(text, getFormat());
 321 
 322         // Convert to the value class if the Value returned from the
 323         // Format does not match.
 324         if (value != null && getValueClass() != null &&
 325                              !getValueClass().isInstance(value)) {
 326             value = super.stringToValue(value.toString());
 327         }
 328         try {
 329             if (!isValidValue(value, true)) {
 330                 throw new ParseException("Value not within min/max range", 0);
 331             }
 332         } catch (ClassCastException cce) {
 333             throw new ParseException("Class cast exception comparing values: "
 334                                      + cce, 0);
 335         }
 336         return value;
 337     }
 338 
 339     /**
 340      * Returns the <code>Format.Field</code> constants associated with
 341      * the text at <code>offset</code>. If <code>offset</code> is not
 342      * a valid location into the current text, this will return an
 343      * empty array.
 344      *
 345      * @param offset offset into text to be examined
 346      * @return Format.Field constants associated with the text at the
 347      *         given position.
 348      */
 349     public Format.Field[] getFields(int offset) {
 350         if (getAllowsInvalid()) {
 351             // This will work if the currently edited value is valid.
 352             updateMask();
 353         }
 354 
 355         Map<Attribute, Object> attrs = getAttributes(offset);
 356 
 357         if (attrs != null && attrs.size() > 0) {
 358             ArrayList<Attribute> al = new ArrayList<Attribute>();
 359 
 360             al.addAll(attrs.keySet());
 361             return al.toArray(EMPTY_FIELD_ARRAY);
 362         }
 363         return EMPTY_FIELD_ARRAY;
 364     }
 365 
 366     /**
 367      * Creates a copy of the DefaultFormatter.
 368      *
 369      * @return copy of the DefaultFormatter
 370      */
 371     public Object clone() throws CloneNotSupportedException {
 372         InternationalFormatter formatter = (InternationalFormatter)super.
 373                                            clone();
 374 
 375         formatter.literalMask = null;
 376         formatter.iterator = null;
 377         formatter.validMask = false;
 378         formatter.string = null;
 379         return formatter;
 380     }
 381 
 382     /**
 383      * If <code>getSupportsIncrement</code> returns true, this returns
 384      * two Actions suitable for incrementing/decrementing the value.
 385      */
 386     protected Action[] getActions() {
 387         if (getSupportsIncrement()) {
 388             return new Action[] { new IncrementAction("increment", 1),
 389                                   new IncrementAction("decrement", -1) };
 390         }
 391         return null;
 392     }
 393 
 394     /**
 395      * Invokes <code>parseObject</code> on <code>f</code>, returning
 396      * its value.
 397      */
 398     Object stringToValue(String text, Format f) throws ParseException {
 399         if (f == null) {
 400             return text;
 401         }
 402         return f.parseObject(text);
 403     }
 404 
 405     /**
 406      * Returns true if <code>value</code> is between the min/max.
 407      *
 408      * @param wantsCCE If false, and a ClassCastException is thrown in
 409      *                 comparing the values, the exception is consumed and
 410      *                 false is returned.
 411      */
 412     boolean isValidValue(Object value, boolean wantsCCE) {
 413         Comparable min = getMinimum();
 414 
 415         try {
 416             if (min != null && min.compareTo(value) > 0) {
 417                 return false;
 418             }
 419         } catch (ClassCastException cce) {
 420             if (wantsCCE) {
 421                 throw cce;
 422             }
 423             return false;
 424         }
 425 
 426         Comparable max = getMaximum();
 427         try {
 428             if (max != null && max.compareTo(value) < 0) {
 429                 return false;
 430             }
 431         } catch (ClassCastException cce) {
 432             if (wantsCCE) {
 433                 throw cce;
 434             }
 435             return false;
 436         }
 437         return true;
 438     }
 439 
 440     /**
 441      * Returns a Set of the attribute identifiers at <code>index</code>.
 442      */
 443     Map<Attribute, Object> getAttributes(int index) {
 444         if (isValidMask()) {
 445             AttributedCharacterIterator iterator = getIterator();
 446 
 447             if (index >= 0 && index <= iterator.getEndIndex()) {
 448                 iterator.setIndex(index);
 449                 return iterator.getAttributes();
 450             }
 451         }
 452         return null;
 453     }
 454 
 455 
 456     /**
 457      * Returns the start of the first run that contains the attribute
 458      * <code>id</code>. This will return <code>-1</code> if the attribute
 459      * can not be found.
 460      */
 461     int getAttributeStart(AttributedCharacterIterator.Attribute id) {
 462         if (isValidMask()) {
 463             AttributedCharacterIterator iterator = getIterator();
 464 
 465             iterator.first();
 466             while (iterator.current() != CharacterIterator.DONE) {
 467                 if (iterator.getAttribute(id) != null) {
 468                     return iterator.getIndex();
 469                 }
 470                 iterator.next();
 471             }
 472         }
 473         return -1;
 474     }
 475 
 476     /**
 477      * Returns the <code>AttributedCharacterIterator</code> used to
 478      * format the last value.
 479      */
 480     AttributedCharacterIterator getIterator() {
 481         return iterator;
 482     }
 483 
 484     /**
 485      * Updates the AttributedCharacterIterator and bitset, if necessary.
 486      */
 487     void updateMaskIfNecessary() {
 488         if (!getAllowsInvalid() && (getFormat() != null)) {
 489             if (!isValidMask()) {
 490                 updateMask();
 491             }
 492             else {
 493                 String newString = getFormattedTextField().getText();
 494 
 495                 if (!newString.equals(string)) {
 496                     updateMask();
 497                 }
 498             }
 499         }
 500     }
 501 
 502     /**
 503      * Updates the AttributedCharacterIterator by invoking
 504      * <code>formatToCharacterIterator</code> on the <code>Format</code>.
 505      * If this is successful,
 506      * <code>updateMask(AttributedCharacterIterator)</code>
 507      * is then invoked to update the internal bitmask.
 508      */
 509     void updateMask() {
 510         if (getFormat() != null) {
 511             Document doc = getFormattedTextField().getDocument();
 512 
 513             validMask = false;
 514             if (doc != null) {
 515                 try {
 516                     string = doc.getText(0, doc.getLength());
 517                 } catch (BadLocationException ble) {
 518                     string = null;
 519                 }
 520                 if (string != null) {
 521                     try {
 522                         Object value = stringToValue(string);
 523                         AttributedCharacterIterator iterator = getFormat().
 524                                   formatToCharacterIterator(value);
 525 
 526                         updateMask(iterator);
 527                     }
 528                     catch (ParseException pe) {}
 529                     catch (IllegalArgumentException iae) {}
 530                     catch (NullPointerException npe) {}
 531                 }
 532             }
 533         }
 534     }
 535 
 536     /**
 537      * Returns the number of literal characters before <code>index</code>.
 538      */
 539     int getLiteralCountTo(int index) {
 540         int lCount = 0;
 541 
 542         for (int counter = 0; counter < index; counter++) {
 543             if (isLiteral(counter)) {
 544                 lCount++;
 545             }
 546         }
 547         return lCount;
 548     }
 549 
 550     /**
 551      * Returns true if the character at index is a literal, that is
 552      * not editable.
 553      */
 554     boolean isLiteral(int index) {
 555         if (isValidMask() && index < string.length()) {
 556             return literalMask.get(index);
 557         }
 558         return false;
 559     }
 560 
 561     /**
 562      * Returns the literal character at index.
 563      */
 564     char getLiteral(int index) {
 565         if (isValidMask() && string != null && index < string.length()) {
 566             return string.charAt(index);
 567         }
 568         return (char)0;
 569     }
 570 
 571     /**
 572      * Returns true if the character at offset is navigatable too. This
 573      * is implemented in terms of <code>isLiteral</code>, subclasses
 574      * may wish to provide different behavior.
 575      */
 576     boolean isNavigatable(int offset) {
 577         return !isLiteral(offset);
 578     }
 579 
 580     /**
 581      * Overriden to update the mask after invoking supers implementation.
 582      */
 583     void updateValue(Object value) {
 584         super.updateValue(value);
 585         updateMaskIfNecessary();
 586     }
 587 
 588     /**
 589      * Overriden to unconditionally allow the replace if
 590      * ignoreDocumentMutate is true.
 591      */
 592     void replace(DocumentFilter.FilterBypass fb, int offset,
 593                      int length, String text,
 594                      AttributeSet attrs) throws BadLocationException {
 595         if (ignoreDocumentMutate) {
 596             fb.replace(offset, length, text, attrs);
 597             return;
 598         }
 599         super.replace(fb, offset, length, text, attrs);
 600     }
 601 
 602     /**
 603      * Returns the index of the next non-literal character starting at
 604      * index. If index is not a literal, it will be returned.
 605      *
 606      * @param direction Amount to increment looking for non-literal
 607      */
 608     private int getNextNonliteralIndex(int index, int direction) {
 609         int max = getFormattedTextField().getDocument().getLength();
 610 
 611         while (index >= 0 && index < max) {
 612             if (isLiteral(index)) {
 613                 index += direction;
 614             }
 615             else {
 616                 return index;
 617             }
 618         }
 619         return (direction == -1) ? 0 : max;
 620     }
 621 
 622     /**
 623      * Overriden in an attempt to honor the literals.
 624      * <p>If we do not allow invalid values and are in overwrite mode, this
 625      * {@code rh.length} is corrected as to preserve trailing literals.
 626      * If not in overwrite mode, and there is text to insert it is
 627      * inserted at the next non literal index going forward.  If there
 628      * is only text to remove, it is removed from the next non literal
 629      * index going backward.
 630      */
 631     boolean canReplace(ReplaceHolder rh) {
 632         if (!getAllowsInvalid()) {
 633             String text = rh.text;
 634             int tl = (text != null) ? text.length() : 0;
 635             JTextComponent c = getFormattedTextField();
 636 
 637             if (tl == 0 && rh.length == 1 && c.getSelectionStart() != rh.offset) {
 638                 // Backspace, adjust to actually delete next non-literal.
 639                 rh.offset = getNextNonliteralIndex(rh.offset, -1);
 640             } else if (getOverwriteMode()) {
 641                 int pos = rh.offset;
 642                 int textPos = pos;
 643                 boolean overflown = false;
 644 
 645                 for (int i = 0; i < rh.length; i++) {
 646                     while (isLiteral(pos)) pos++;
 647                     if (pos >= string.length()) {
 648                         pos = textPos;
 649                         overflown = true;
 650                         break;
 651                     }
 652                     textPos = ++pos;
 653                 }
 654                 if (overflown || c.getSelectedText() == null) {
 655                     rh.length = pos - rh.offset;
 656                 }
 657             }
 658             else if (tl > 0) {
 659                 // insert (or insert and remove)
 660                 rh.offset = getNextNonliteralIndex(rh.offset, 1);
 661             }
 662             else {
 663                 // remove only
 664                 rh.offset = getNextNonliteralIndex(rh.offset, -1);
 665             }
 666             ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
 667             ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
 668                                                     rh.text.length() : 0;
 669         }
 670         else {
 671             ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
 672             ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
 673                                                     rh.text.length() : 0;
 674         }
 675         boolean can = super.canReplace(rh);
 676         if (can && !getAllowsInvalid()) {
 677             ((ExtendedReplaceHolder)rh).resetFromValue(this);
 678         }
 679         return can;
 680     }
 681 
 682     /**
 683      * When in !allowsInvalid mode the text is reset on every edit, thus
 684      * supers implementation will position the cursor at the wrong position.
 685      * As such, this invokes supers implementation and then invokes
 686      * <code>repositionCursor</code> to correctly reset the cursor.
 687      */
 688     boolean replace(ReplaceHolder rh) throws BadLocationException {
 689         int start = -1;
 690         int direction = 1;
 691         int literalCount = -1;
 692 
 693         if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
 694                (getFormattedTextField().getSelectionStart() != rh.offset ||
 695                    rh.length > 1)) {
 696             direction = -1;
 697         }
 698         if (!getAllowsInvalid()) {
 699             if ((rh.text == null || rh.text.length() == 0) && rh.length > 0) {
 700                 // remove
 701                 start = getFormattedTextField().getSelectionStart();
 702             }
 703             else {
 704                 start = rh.offset;
 705             }
 706             literalCount = getLiteralCountTo(start);
 707         }
 708         if (super.replace(rh)) {
 709             if (start != -1) {
 710                 int end = ((ExtendedReplaceHolder)rh).endOffset;
 711 
 712                 end += ((ExtendedReplaceHolder)rh).endTextLength;
 713                 repositionCursor(literalCount, end, direction);
 714             }
 715             else {
 716                 start = ((ExtendedReplaceHolder)rh).endOffset;
 717                 if (direction == 1) {
 718                     start += ((ExtendedReplaceHolder)rh).endTextLength;
 719                 }
 720                 repositionCursor(start, direction);
 721             }
 722             return true;
 723         }
 724         return false;
 725     }
 726 
 727     /**
 728      * Repositions the cursor. <code>startLiteralCount</code> gives
 729      * the number of literals to the start of the deleted range, end
 730      * gives the ending location to adjust from, direction gives
 731      * the direction relative to <code>end</code> to position the
 732      * cursor from.
 733      */
 734     private void repositionCursor(int startLiteralCount, int end,
 735                                   int direction)  {
 736         int endLiteralCount = getLiteralCountTo(end);
 737 
 738         if (endLiteralCount != end) {
 739             end -= startLiteralCount;
 740             for (int counter = 0; counter < end; counter++) {
 741                 if (isLiteral(counter)) {
 742                     end++;
 743                 }
 744             }
 745         }
 746         repositionCursor(end, 1 /*direction*/);
 747     }
 748 
 749     /**
 750      * Returns the character from the mask that has been buffered
 751      * at <code>index</code>.
 752      */
 753     char getBufferedChar(int index) {
 754         if (isValidMask()) {
 755             if (string != null && index < string.length()) {
 756                 return string.charAt(index);
 757             }
 758         }
 759         return (char)0;
 760     }
 761 
 762     /**
 763      * Returns true if the current mask is valid.
 764      */
 765     boolean isValidMask() {
 766         return validMask;
 767     }
 768 
 769     /**
 770      * Returns true if <code>attributes</code> is null or empty.
 771      */
 772     boolean isLiteral(Map attributes) {
 773         return ((attributes == null) || attributes.size() == 0);
 774     }
 775 
 776     /**
 777      * Updates the interal bitset from <code>iterator</code>. This will
 778      * set <code>validMask</code> to true if <code>iterator</code> is
 779      * non-null.
 780      */
 781     private void updateMask(AttributedCharacterIterator iterator) {
 782         if (iterator != null) {
 783             validMask = true;
 784             this.iterator = iterator;
 785 
 786             // Update the literal mask
 787             if (literalMask == null) {
 788                 literalMask = new BitSet();
 789             }
 790             else {
 791                 for (int counter = literalMask.length() - 1; counter >= 0;
 792                      counter--) {
 793                     literalMask.clear(counter);
 794                 }
 795             }
 796 
 797             iterator.first();
 798             while (iterator.current() != CharacterIterator.DONE) {
 799                 Map attributes = iterator.getAttributes();
 800                 boolean set = isLiteral(attributes);
 801                 int start = iterator.getIndex();
 802                 int end = iterator.getRunLimit();
 803 
 804                 while (start < end) {
 805                     if (set) {
 806                         literalMask.set(start);
 807                     }
 808                     else {
 809                         literalMask.clear(start);
 810                     }
 811                     start++;
 812                 }
 813                 iterator.setIndex(start);
 814             }
 815         }
 816     }
 817 
 818     /**
 819      * Returns true if <code>field</code> is non-null.
 820      * Subclasses that wish to allow incrementing to happen outside of
 821      * the known fields will need to override this.
 822      */
 823     boolean canIncrement(Object field, int cursorPosition) {
 824         return (field != null);
 825     }
 826 
 827     /**
 828      * Selects the fields identified by <code>attributes</code>.
 829      */
 830     void selectField(Object f, int count) {
 831         AttributedCharacterIterator iterator = getIterator();
 832 
 833         if (iterator != null &&
 834                         (f instanceof AttributedCharacterIterator.Attribute)) {
 835             AttributedCharacterIterator.Attribute field =
 836                                    (AttributedCharacterIterator.Attribute)f;
 837 
 838             iterator.first();
 839             while (iterator.current() != CharacterIterator.DONE) {
 840                 while (iterator.getAttribute(field) == null &&
 841                        iterator.next() != CharacterIterator.DONE);
 842                 if (iterator.current() != CharacterIterator.DONE) {
 843                     int limit = iterator.getRunLimit(field);
 844 
 845                     if (--count <= 0) {
 846                         getFormattedTextField().select(iterator.getIndex(),
 847                                                        limit);
 848                         break;
 849                     }
 850                     iterator.setIndex(limit);
 851                     iterator.next();
 852                 }
 853             }
 854         }
 855     }
 856 
 857     /**
 858      * Returns the field that will be adjusted by adjustValue.
 859      */
 860     Object getAdjustField(int start, Map attributes) {
 861         return null;
 862     }
 863 
 864     /**
 865      * Returns the number of occurences of <code>f</code> before
 866      * the location <code>start</code> in the current
 867      * <code>AttributedCharacterIterator</code>.
 868      */
 869     private int getFieldTypeCountTo(Object f, int start) {
 870         AttributedCharacterIterator iterator = getIterator();
 871         int count = 0;
 872 
 873         if (iterator != null &&
 874                     (f instanceof AttributedCharacterIterator.Attribute)) {
 875             AttributedCharacterIterator.Attribute field =
 876                                    (AttributedCharacterIterator.Attribute)f;
 877 
 878             iterator.first();
 879             while (iterator.getIndex() < start) {
 880                 while (iterator.getAttribute(field) == null &&
 881                        iterator.next() != CharacterIterator.DONE);
 882                 if (iterator.current() != CharacterIterator.DONE) {
 883                     iterator.setIndex(iterator.getRunLimit(field));
 884                     iterator.next();
 885                     count++;
 886                 }
 887                 else {
 888                     break;
 889                 }
 890             }
 891         }
 892         return count;
 893     }
 894 
 895     /**
 896      * Subclasses supporting incrementing must override this to handle
 897      * the actual incrementing. <code>value</code> is the current value,
 898      * <code>attributes</code> gives the field the cursor is in (may be
 899      * null depending upon <code>canIncrement</code>) and
 900      * <code>direction</code> is the amount to increment by.
 901      */
 902     Object adjustValue(Object value, Map attributes, Object field,
 903                            int direction) throws
 904                       BadLocationException, ParseException {
 905         return null;
 906     }
 907 
 908     /**
 909      * Returns false, indicating InternationalFormatter does not allow
 910      * incrementing of the value. Subclasses that wish to support
 911      * incrementing/decrementing the value should override this and
 912      * return true. Subclasses should also override
 913      * <code>adjustValue</code>.
 914      */
 915     boolean getSupportsIncrement() {
 916         return false;
 917     }
 918 
 919     /**
 920      * Resets the value of the JFormattedTextField to be
 921      * <code>value</code>.
 922      */
 923     void resetValue(Object value) throws BadLocationException, ParseException {
 924         Document doc = getFormattedTextField().getDocument();
 925         String string = valueToString(value);
 926 
 927         try {
 928             ignoreDocumentMutate = true;
 929             doc.remove(0, doc.getLength());
 930             doc.insertString(0, string, null);
 931         } finally {
 932             ignoreDocumentMutate = false;
 933         }
 934         updateValue(value);
 935     }
 936 
 937     /**
 938      * Subclassed to update the internal representation of the mask after
 939      * the default read operation has completed.
 940      */
 941     private void readObject(ObjectInputStream s)
 942         throws IOException, ClassNotFoundException {
 943         s.defaultReadObject();
 944         updateMaskIfNecessary();
 945     }
 946 
 947 
 948     /**
 949      * Overriden to return an instance of <code>ExtendedReplaceHolder</code>.
 950      */
 951     ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb, int offset,
 952                                    int length, String text,
 953                                    AttributeSet attrs) {
 954         if (replaceHolder == null) {
 955             replaceHolder = new ExtendedReplaceHolder();
 956         }
 957         return super.getReplaceHolder(fb, offset, length, text, attrs);
 958     }
 959 
 960 
 961     /**
 962      * As InternationalFormatter replaces the complete text on every edit,
 963      * ExtendedReplaceHolder keeps track of the offset and length passed
 964      * into canReplace.
 965      */
 966     static class ExtendedReplaceHolder extends ReplaceHolder {
 967         /** Offset of the insert/remove. This may differ from offset in
 968          * that if !allowsInvalid the text is replaced on every edit. */
 969         int endOffset;
 970         /** Length of the text. This may differ from text.length in
 971          * that if !allowsInvalid the text is replaced on every edit. */
 972         int endTextLength;
 973 
 974         /**
 975          * Resets the region to delete to be the complete document and
 976          * the text from invoking valueToString on the current value.
 977          */
 978         void resetFromValue(InternationalFormatter formatter) {
 979             // Need to reset the complete string as Format's result can
 980             // be completely different.
 981             offset = 0;
 982             try {
 983                 text = formatter.valueToString(value);
 984             } catch (ParseException pe) {
 985                 // Should never happen, otherwise canReplace would have
 986                 // returned value.
 987                 text = "";
 988             }
 989             length = fb.getDocument().getLength();
 990         }
 991     }
 992 
 993 
 994     /**
 995      * IncrementAction is used to increment the value by a certain amount.
 996      * It calls into <code>adjustValue</code> to handle the actual
 997      * incrementing of the value.
 998      */
 999     private class IncrementAction extends AbstractAction {
1000         private int direction;
1001 
1002         IncrementAction(String name, int direction) {
1003             super(name);
1004             this.direction = direction;
1005         }
1006 
1007         public void actionPerformed(ActionEvent ae) {
1008 
1009             if (getFormattedTextField().isEditable()) {
1010                 if (getAllowsInvalid()) {
1011                     // This will work if the currently edited value is valid.
1012                     updateMask();
1013                 }
1014 
1015                 boolean validEdit = false;
1016 
1017                 if (isValidMask()) {
1018                     int start = getFormattedTextField().getSelectionStart();
1019 
1020                     if (start != -1) {
1021                         AttributedCharacterIterator iterator = getIterator();
1022 
1023                         iterator.setIndex(start);
1024 
1025                         Map attributes = iterator.getAttributes();
1026                         Object field = getAdjustField(start, attributes);
1027 
1028                         if (canIncrement(field, start)) {
1029                             try {
1030                                 Object value = stringToValue(
1031                                         getFormattedTextField().getText());
1032                                 int fieldTypeCount = getFieldTypeCountTo(
1033                                         field, start);
1034 
1035                                 value = adjustValue(value, attributes,
1036                                         field, direction);
1037                                 if (value != null && isValidValue(value, false)) {
1038                                     resetValue(value);
1039                                     updateMask();
1040 
1041                                     if (isValidMask()) {
1042                                         selectField(field, fieldTypeCount);
1043                                     }
1044                                     validEdit = true;
1045                                 }
1046                             }
1047                             catch (ParseException pe) { }
1048                             catch (BadLocationException ble) { }
1049                         }
1050                     }
1051                 }
1052                 if (!validEdit) {
1053                     invalidEdit();
1054                 }
1055             }
1056         }
1057     }
1058 }