/*
* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text;
import java.awt.event.ActionEvent;
import java.io.*;
import java.text.*;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.*;
import javax.swing.*;
/**
* InternationalFormatter extends DefaultFormatter,
* using an instance of java.text.Format to handle the
* conversion to a String, and the conversion from a String.
*
* If getAllowsInvalid() is false, this will ask the
* Format to format the current text on every edit.
*
* You can specify a minimum and maximum value by way of the
* setMinimum and setMaximum methods. In order
* for this to work the values returned from stringToValue must be
* comparable to the min/max values by way of the Comparable
* interface.
*
* Be careful how you configure the Format and the
* InternationalFormatter, as it is possible to create a
* situation where certain values can not be input. Consider the date
* format 'M/d/yy', an InternationalFormatter that is always
* valid (setAllowsInvalid(false)), is in overwrite mode
* (setOverwriteMode(true)) and the date 7/1/99. In this
* case the user will not be able to enter a two digit month or day of
* month. To avoid this, the format should be 'MM/dd/yy'.
*
* If InternationalFormatter is configured to only allow valid
* values (setAllowsInvalid(false)), every valid edit will result
* in the text of the JFormattedTextField being completely reset
* from the Format.
* The cursor position will also be adjusted as literal characters are
* added/removed from the resulting String.
*
* InternationalFormatter's behavior of
* stringToValue is slightly different than that of
* DefaultTextFormatter, it does the following:
*
parseObject is invoked on the Format
* specified by setFormat
* setValueClass),
* supers implementation is invoked to convert the value returned
* from parseObject to the appropriate class.
* ParseException has not been thrown, and the value
* is outside the min/max a ParseException is thrown.
* InternationalFormatter implements stringToValue
* in this manner so that you can specify an alternate Class than
* Format may return.
*
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*
* @see java.text.Format
* @see java.lang.Comparable
*
* @since 1.4
*/
@SuppressWarnings("serial") // Same-version serialization only
public class InternationalFormatter extends DefaultFormatter {
/**
* Used by getFields.
*/
private static final Format.Field[] EMPTY_FIELD_ARRAY =new Format.Field[0];
/**
* Object used to handle the conversion.
*/
private Format format;
/**
* Can be used to impose a maximum value.
*/
private Comparable> max;
/**
* Can be used to impose a minimum value.
*/
private Comparable> min;
/**
* InternationalFormatter's behavior is dicatated by a
* AttributedCharacterIterator that is obtained from
* the Format. On every edit, assuming
* allows invalid is false, the Format instance is invoked
* with formatToCharacterIterator. A BitSet is
* also kept upto date with the non-literal characters, that is
* for every index in the AttributedCharacterIterator an
* entry in the bit set is updated based on the return value from
* isLiteral(Map). isLiteral(int) then uses
* this cached information.
*
* If allowsInvalid is false, every edit results in resetting the complete * text of the JTextComponent. *
* InternationalFormatterFilter can also provide two actions suitable for
* incrementing and decrementing. To enable this a subclass must
* override getSupportsIncrement to return true, and
* override adjustValue to handle the changing of the
* value. If you want to support changing the value outside of
* the valid FieldPositions, you will need to override
* canIncrement.
*/
/**
* A bit is set for every index identified in the
* AttributedCharacterIterator that is not considered decoration.
* This should only be used if validMask is true.
*/
private transient BitSet literalMask;
/**
* Used to iterate over characters.
*/
private transient AttributedCharacterIterator iterator;
/**
* True if the Format was able to convert the value to a String and
* back.
*/
private transient boolean validMask;
/**
* Current value being displayed.
*/
private transient String string;
/**
* If true, DocumentFilter methods are unconditionally allowed,
* and no checking is done on their values. This is used when
* incrementing/decrementing via the actions.
*/
private transient boolean ignoreDocumentMutate;
/**
* Creates an InternationalFormatter with no
* Format specified.
*/
public InternationalFormatter() {
setOverwriteMode(false);
}
/**
* Creates an InternationalFormatter with the specified
* Format instance.
*
* @param format Format instance used for converting from/to Strings
*/
public InternationalFormatter(Format format) {
this();
setFormat(format);
}
/**
* Sets the format that dictates the legal values that can be edited
* and displayed.
*
* @param format Format instance used for converting
* from/to Strings
*/
public void setFormat(Format format) {
this.format = format;
}
/**
* Returns the format that dictates the legal values that can be edited
* and displayed.
*
* @return Format instance used for converting from/to Strings
*/
public Format getFormat() {
return format;
}
/**
* Sets the minimum permissible value. If the valueClass has
* not been specified, and minimum is non null, the
* valueClass will be set to that of the class of
* minimum.
*
* @param minimum Minimum legal value that can be input
* @see #setValueClass
*/
public void setMinimum(Comparable> minimum) {
if (getValueClass() == null && minimum != null) {
setValueClass(minimum.getClass());
}
min = minimum;
}
/**
* Returns the minimum permissible value.
*
* @return Minimum legal value that can be input
*/
public Comparable> getMinimum() {
return min;
}
/**
* Sets the maximum permissible value. If the valueClass has
* not been specified, and max is non null, the
* valueClass will be set to that of the class of
* max.
*
* @param max Maximum legal value that can be input
* @see #setValueClass
*/
public void setMaximum(Comparable> max) {
if (getValueClass() == null && max != null) {
setValueClass(max.getClass());
}
this.max = max;
}
/**
* Returns the maximum permissible value.
*
* @return Maximum legal value that can be input
*/
public Comparable> getMaximum() {
return max;
}
/**
* Installs the DefaultFormatter onto a particular
* JFormattedTextField.
* This will invoke valueToString to convert the
* current value from the JFormattedTextField to
* a String. This will then install the Actions from
* getActions, the DocumentFilter
* returned from getDocumentFilter and the
* NavigationFilter returned from
* getNavigationFilter onto the
* JFormattedTextField.
*
* Subclasses will typically only need to override this if they
* wish to install additional listeners on the
* JFormattedTextField.
*
* If there is a ParseException in converting the
* current value to a String, this will set the text to an empty
* String, and mark the JFormattedTextField as being
* in an invalid state.
*
* While this is a public method, this is typically only useful
* for subclassers of JFormattedTextField.
* JFormattedTextField will invoke this method at
* the appropriate times when the value changes, or its internal
* state changes.
*
* @param ftf JFormattedTextField to format for, may be null indicating
* uninstall from current JFormattedTextField.
*/
public void install(JFormattedTextField ftf) {
super.install(ftf);
updateMaskIfNecessary();
// invoked again as the mask should now be valid.
positionCursorAtInitialLocation();
}
/**
* Returns a String representation of the Object value.
* This invokes format on the current Format.
*
* @throws ParseException if there is an error in the conversion
* @param value Value to convert
* @return String representation of value
*/
public String valueToString(Object value) throws ParseException {
if (value == null) {
return "";
}
Format f = getFormat();
if (f == null) {
return value.toString();
}
return f.format(value);
}
/**
* Returns the Object representation of the
* String text.
*
* @param text String to convert
* @return Object representation of text
* @throws ParseException if there is an error in the conversion
*/
public Object stringToValue(String text) throws ParseException {
Object value = stringToValue(text, getFormat());
// Convert to the value class if the Value returned from the
// Format does not match.
if (value != null && getValueClass() != null &&
!getValueClass().isInstance(value)) {
value = super.stringToValue(value.toString());
}
try {
if (!isValidValue(value, true)) {
throw new ParseException("Value not within min/max range", 0);
}
} catch (ClassCastException cce) {
throw new ParseException("Class cast exception comparing values: "
+ cce, 0);
}
return value;
}
/**
* Returns the Format.Field constants associated with
* the text at offset. If offset is not
* a valid location into the current text, this will return an
* empty array.
*
* @param offset offset into text to be examined
* @return Format.Field constants associated with the text at the
* given position.
*/
public Format.Field[] getFields(int offset) {
if (getAllowsInvalid()) {
// This will work if the currently edited value is valid.
updateMask();
}
MapgetSupportsIncrement returns true, this returns
* two Actions suitable for incrementing/decrementing the value.
*/
protected Action[] getActions() {
if (getSupportsIncrement()) {
return new Action[] { new IncrementAction("increment", 1),
new IncrementAction("decrement", -1) };
}
return null;
}
/**
* Invokes parseObject on f, returning
* its value.
*/
Object stringToValue(String text, Format f) throws ParseException {
if (f == null) {
return text;
}
return f.parseObject(text);
}
/**
* Returns true if value is between the min/max.
*
* @param wantsCCE If false, and a ClassCastException is thrown in
* comparing the values, the exception is consumed and
* false is returned.
*/
boolean isValidValue(Object value, boolean wantsCCE) {
@SuppressWarnings("unchecked")
Comparable