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™
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);
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,
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);
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;
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
|
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} extends {@code DefaultFormatter},
36 * using an instance of {@code java.text.Format} to handle the
37 * conversion to a String, and the conversion from a String.
38 * <p>
39 * If {@code getAllowsInvalid()} is false, this will ask the
40 * {@code Format} 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} and {@code setMaximum} methods. In order
44 * for this to work the values returned from {@code stringToValue} must be
45 * comparable to the min/max values by way of the {@code Comparable}
46 * interface.
47 * <p>
48 * Be careful how you configure the {@code Format} and the
49 * {@code InternationalFormatter}, 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} that is always
52 * valid ({@code setAllowsInvalid(false)}), is in overwrite mode
53 * ({@code setOverwriteMode(true)}) 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} is configured to only allow valid
58 * values ({@code setAllowsInvalid(false)}), every valid edit will result
59 * in the text of the {@code JFormattedTextField} being completely reset
60 * from the {@code Format}.
61 * The cursor position will also be adjusted as literal characters are
62 * added/removed from the resulting String.
63 * <p>
64 * {@code InternationalFormatter}'s behavior of
65 * {@code stringToValue} is slightly different than that of
66 * {@code DefaultTextFormatter}, it does the following:
67 * <ol>
68 * <li>{@code parseObject} is invoked on the {@code Format}
69 * specified by {@code setFormat}
70 * <li>If a Class has been set for the values ({@code setValueClass}),
71 * supers implementation is invoked to convert the value returned
72 * from {@code parseObject} to the appropriate class.
73 * <li>If a {@code ParseException} has not been thrown, and the value
74 * is outside the min/max a {@code ParseException} is thrown.
75 * <li>The value is returned.
76 * </ol>
77 * {@code InternationalFormatter} implements {@code stringToValue}
78 * in this manner so that you can specify an alternate Class than
79 * {@code Format} 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™
87 * has been added to the {@code java.beans} 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}.
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}'s behavior is dicatated by a
117 * {@code AttributedCharacterIterator} that is obtained from
118 * the {@code Format}. On every edit, assuming
119 * allows invalid is false, the {@code Format} instance is invoked
120 * with {@code formatToCharacterIterator}. A {@code BitSet} is
121 * also kept upto date with the non-literal characters, that is
122 * for every index in the {@code AttributedCharacterIterator} an
123 * entry in the bit set is updated based on the return value from
124 * {@code isLiteral(Map)}. {@code isLiteral(int)} 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} to return true, and
133 * override {@code adjustValue} 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}.
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} with no
167 * {@code Format} specified.
168 */
169 public InternationalFormatter() {
170 setOverwriteMode(false);
171 }
172
173 /**
174 * Creates an {@code InternationalFormatter} with the specified
175 * {@code Format} 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} 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} has
207 * not been specified, and {@code minimum} is non null, the
208 * {@code valueClass} will be set to that of the class of
209 * {@code minimum}.
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} has
232 * not been specified, and {@code max} is non null, the
233 * {@code valueClass} will be set to that of the class of
234 * {@code max}.
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} onto a particular
257 * {@code JFormattedTextField}.
258 * This will invoke {@code valueToString} to convert the
259 * current value from the {@code JFormattedTextField} to
260 * a String. This will then install the {@code Action}s from
261 * {@code getActions}, the {@code DocumentFilter}
262 * returned from {@code getDocumentFilter} and the
263 * {@code NavigationFilter} returned from
264 * {@code getNavigationFilter} onto the
265 * {@code JFormattedTextField}.
266 * <p>
267 * Subclasses will typically only need to override this if they
268 * wish to install additional listeners on the
269 * {@code JFormattedTextField}.
270 * <p>
271 * If there is a {@code ParseException} in converting the
272 * current value to a String, this will set the text to an empty
273 * String, and mark the {@code JFormattedTextField} 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}.
278 * {@code JFormattedTextField} 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}.
294 * This invokes {@code format} on the current {@code Format}.
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} representation of the
314 * {@code String text}.
315 *
316 * @param text {@code String} to convert
317 * @return {@code Object} 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} constants associated with
342 * the text at {@code offset}. If {@code offset} 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);
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} 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} on {@code f}, 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} 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}.
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}. This will return {@code -1} 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} 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} on the {@code Format}.
508 * If this is successful,
509 * {@code updateMask(AttributedCharacterIterator)}
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}.
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}, 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,
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} 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);
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} 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} 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}.
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} 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}. This will
781 * set {@code validMask} to true if {@code iterator} 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} 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}.
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} before
869 * the location {@code start} in the current
870 * {@code AttributedCharacterIterator}.
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} is the current value,
901 * {@code attributes} gives the field the cursor is in (may be
902 * null depending upon {@code canIncrement}) and
903 * {@code direction} 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}.
917 */
918 boolean getSupportsIncrement() {
919 return false;
920 }
921
922 /**
923 * Resets the value of the JFormattedTextField to be
924 * {@code value}.
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}.
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;
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} 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
|