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 */
25package javax.swing.text;
26
27import java.awt.event.ActionEvent;
28import java.io.*;
29import java.text.*;
30import java.text.AttributedCharacterIterator.Attribute;
31import java.util.*;
32import 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
96public 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}
1062