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