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™ 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