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