FileDocCategorySizeDatePackage
InternationalFormatter.javaAPI DocJava SE 6 API38037Tue Jun 10 00:26:58 BST 2008javax.swing.text

InternationalFormatter

public class InternationalFormatter extends DefaultFormatter
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:

  1. parseObject is invoked on the Format specified by setFormat
  2. If a Class has been set for the values (setValueClass), supers implementation is invoked to convert the value returned from parseObject to the appropriate class.
  3. If a ParseException has not been thrown, and the value is outside the min/max a ParseException is thrown.
  4. The value is returned.
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 JavaBeansTM has been added to the java.beans package. Please see {@link java.beans.XMLEncoder}.

see
java.text.Format
see
java.lang.Comparable
version
1.7 04/09/01
since
1.4

Fields Summary
private static final Format$Field[]
EMPTY_FIELD_ARRAY
Used by getFields.
private Format
format
Object used to handle the conversion.
private Comparable
max
Can be used to impose a maximum value.
private Comparable
min
Can be used to impose a minimum value.
private transient BitSet
literalMask
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 AttributedCharacterIterator
iterator
Used to iterate over characters.
private transient boolean
validMask
True if the Format was able to convert the value to a String and back.
private transient String
string
Current value being displayed.
private transient boolean
ignoreDocumentMutate
If true, DocumentFilter methods are unconditionally allowed, and no checking is done on their values. This is used when incrementing/decrementing via the actions.
Constructors Summary
public InternationalFormatter()
Creates an InternationalFormatter with no Format specified.



                
      
        setOverwriteMode(false);
    
public InternationalFormatter(Format format)
Creates an InternationalFormatter with the specified Format instance.

param
format Format instance used for converting from/to Strings

        this();
        setFormat(format);
    
Methods Summary
java.lang.ObjectadjustValue(java.lang.Object value, java.util.Map attributes, java.lang.Object field, int direction)
Subclasses supporting incrementing must override this to handle the actual incrementing. value is the current value, attributes gives the field the cursor is in (may be null depending upon canIncrement) and direction is the amount to increment by.

        return null;
    
booleancanIncrement(java.lang.Object field, int cursorPosition)
Returns true if field is non-null. Subclasses that wish to allow incrementing to happen outside of the known fields will need to override this.

        return (field != null);
    
booleancanReplace(ReplaceHolder rh)
Overriden in an attempt to honor the literals.

If we do not allow invalid values and are in overwrite mode, this does the following for each character in the replacement range:

  1. If the character is a literal, add it to the string to replace with. If there is text to insert and it doesn't match the literal, then insert the literal in the the middle of the insert text. This allows you to either paste in literals or not and get the same behavior.
  2. If there is no text to insert, replace it with ' '.
If not in overwrite mode, and there is text to insert it is inserted at the next non literal index going forward. If there is only text to remove, it is removed from the next non literal index going backward.

        if (!getAllowsInvalid()) {
            String text = rh.text;
            int tl = (text != null) ? text.length() : 0;

            if (tl == 0 && rh.length == 1 && getFormattedTextField().
                              getSelectionStart() != rh.offset) {
                // Backspace, adjust to actually delete next non-literal.
                rh.offset = getNextNonliteralIndex(rh.offset, -1);
            }
            if (getOverwriteMode()) {
                StringBuffer replace = null;

                for (int counter = 0, textIndex = 0,
                         max = Math.max(tl, rh.length); counter < max;
                         counter++) {
                    if (isLiteral(rh.offset + counter)) {
                        if (replace != null) {
                            replace.append(getLiteral(rh.offset +
                                                      counter));
                        }
                        if (textIndex < tl && text.charAt(textIndex) ==
                                      getLiteral(rh.offset + counter)) {
                            textIndex++;
                        }
                        else if (textIndex == 0) {
                            rh.offset++;
                            rh.length--;
                            counter--;
                            max--;
                        }
                        else if (replace == null) {
                            replace = new StringBuffer(max);
                            replace.append(text.substring(0, textIndex));
                            replace.append(getLiteral(rh.offset +
                                                      counter));
                        }
                    }
                    else if (textIndex < tl) {
                        if (replace != null) {
                            replace.append(text.charAt(textIndex));
                        }
                        textIndex++;
                    }
                    else {
                        // Nothing to replace it with, assume ' '
                        if (replace == null) {
                            replace = new StringBuffer(max);
                            if (textIndex > 0) {
                                replace.append(text.substring(0, textIndex));
                            }
                        }
                        if (replace != null) {
                            replace.append(' ");
                        }
                    }
                }
                if (replace != null) {
                    rh.text = replace.toString();
                }
            }
            else if (tl > 0) {
                // insert (or insert and remove)
                rh.offset = getNextNonliteralIndex(rh.offset, 1);
            }
            else {
                // remove only
                rh.offset = getNextNonliteralIndex(rh.offset, -1);
            }
            ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
            ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
                                                    rh.text.length() : 0;
        }
        else {
            ((ExtendedReplaceHolder)rh).endOffset = rh.offset;
            ((ExtendedReplaceHolder)rh).endTextLength = (rh.text != null) ?
                                                    rh.text.length() : 0;
        }
        boolean can = super.canReplace(rh);
        if (can && !getAllowsInvalid()) {
            ((ExtendedReplaceHolder)rh).resetFromValue(this);
        }
        return can;
    
public java.lang.Objectclone()
Creates a copy of the DefaultFormatter.

return
copy of the DefaultFormatter

        InternationalFormatter formatter = (InternationalFormatter)super.
                                           clone();

        formatter.literalMask = null;
        formatter.iterator = null;
        formatter.validMask = false;
        formatter.string = null;
        return formatter;
    
protected javax.swing.Action[]getActions()
If getSupportsIncrement returns true, this returns two Actions suitable for incrementing/decrementing the value.

        if (getSupportsIncrement()) {
            return new Action[] { new IncrementAction("increment", 1),
                                  new IncrementAction("decrement", -1) };
        }
        return null;
    
java.lang.ObjectgetAdjustField(int start, java.util.Map attributes)
Returns the field that will be adjusted by adjustValue.

        return null;
    
intgetAttributeStart(java.text.AttributedCharacterIterator$Attribute id)
Returns the start of the first run that contains the attribute id. This will return -1 if the attribute can not be found.

        if (isValidMask()) {
            AttributedCharacterIterator iterator = getIterator();

            iterator.first();
            while (iterator.current() != CharacterIterator.DONE) {
                if (iterator.getAttribute(id) != null) {
                    return iterator.getIndex();
                }
                iterator.next();
            }
        }
        return -1;
    
java.util.MapgetAttributes(int index)
Returns a Set of the attribute identifiers at index.

        if (isValidMask()) {
            AttributedCharacterIterator iterator = getIterator();

            if (index >= 0 && index <= iterator.getEndIndex()) {
                iterator.setIndex(index);
                return iterator.getAttributes();
            }
        }
        return null;
    
chargetBufferedChar(int index)
Returns the character from the mask that has been buffered at index.

        if (isValidMask()) {
            if (string != null && index < string.length()) {
                return string.charAt(index);
            }
        }
        return (char)0;
    
private intgetFieldTypeCountTo(java.lang.Object f, int start)
Returns the number of occurences of f before the location start in the current AttributedCharacterIterator.

        AttributedCharacterIterator iterator = getIterator();
        int count = 0;

        if (iterator != null &&
                    (f instanceof AttributedCharacterIterator.Attribute)) {
            AttributedCharacterIterator.Attribute field =
                                   (AttributedCharacterIterator.Attribute)f;
            int index = 0;

            iterator.first();
            while (iterator.getIndex() < start) {
                while (iterator.getAttribute(field) == null &&
                       iterator.next() != CharacterIterator.DONE);
                if (iterator.current() != CharacterIterator.DONE) {
                    iterator.setIndex(iterator.getRunLimit(field));
                    iterator.next();
                    count++;
                }
                else {
                    break;
                }
            }
        }
        return count;
    
public java.text.Format$Field[]getFields(int offset)
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.

        if (getAllowsInvalid()) {
            // This will work if the currently edited value is valid.
            updateMask();
        }

        Map attrs = getAttributes(offset);

        if (attrs != null && attrs.size() > 0) {
            ArrayList al = new ArrayList();

            al.addAll(attrs.keySet());
            return (Format.Field[])al.toArray(EMPTY_FIELD_ARRAY);
        }
        return EMPTY_FIELD_ARRAY;
    
public java.text.FormatgetFormat()
Returns the format that dictates the legal values that can be edited and displayed.

return
Format instance used for converting from/to Strings

        return format;
    
java.text.AttributedCharacterIteratorgetIterator()
Returns the AttributedCharacterIterator used to format the last value.

        return iterator;
    
chargetLiteral(int index)
Returns the literal character at index.

        if (isValidMask() && string != null && index < string.length()) {
            return string.charAt(index);
        }
        return (char)0;
    
intgetLiteralCountTo(int index)
Returns the number of literal characters before index.

        int lCount = 0;

        for (int counter = 0; counter < index; counter++) {
            if (isLiteral(counter)) {
                lCount++;
            }
        }
        return lCount;
    
public java.lang.ComparablegetMaximum()
Returns the maximum permissible value.

return
Maximum legal value that can be input

        return max;
    
public java.lang.ComparablegetMinimum()
Returns the minimum permissible value.

return
Minimum legal value that can be input

        return min;
    
private intgetNextNonliteralIndex(int index, int direction)
Returns the index of the next non-literal character starting at index. If index is not a literal, it will be returned.

param
direction Amount to increment looking for non-literal

        int max = getFormattedTextField().getDocument().getLength();

        while (index >= 0 && index < max) {
            if (isLiteral(index)) {
                index += direction;
            }
            else {
                return index;
            }
        }
        return (direction == -1) ? 0 : max;
    
ReplaceHoldergetReplaceHolder(javax.swing.text.DocumentFilter$FilterBypass fb, int offset, int length, java.lang.String text, javax.swing.text.AttributeSet attrs)
Overriden to return an instance of ExtendedReplaceHolder.

        if (replaceHolder == null) {
            replaceHolder = new ExtendedReplaceHolder();
        }
        return super.getReplaceHolder(fb, offset, length, text, attrs);
    
booleangetSupportsIncrement()
Returns false, indicating InternationalFormatter does not allow incrementing of the value. Subclasses that wish to support incrementing/decrementing the value should override this and return true. Subclasses should also override adjustValue.

        return false;
    
public voidinstall(javax.swing.JFormattedTextField ftf)
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.

        super.install(ftf);
        updateMaskIfNecessary();
        // invoked again as the mask should now be valid.
        positionCursorAtInitialLocation();
    
booleanisLiteral(int index)
Returns true if the character at index is a literal, that is not editable.

        if (isValidMask() && index < string.length()) {
            return literalMask.get(index);
        }
        return false;
    
booleanisLiteral(java.util.Map attributes)
Returns true if attributes is null or empty.

        return ((attributes == null) || attributes.size() == 0);
    
booleanisNavigatable(int offset)
Returns true if the character at offset is navigatable too. This is implemented in terms of isLiteral, subclasses may wish to provide different behavior.

        return !isLiteral(offset);
    
booleanisValidMask()
Returns true if the current mask is valid.

        return validMask;
    
booleanisValidValue(java.lang.Object value, boolean wantsCCE)
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.

        Comparable min = getMinimum();

        try {
            if (min != null && min.compareTo(value) > 0) {
                return false;
            }
        } catch (ClassCastException cce) {
            if (wantsCCE) {
                throw cce;
            }
            return false;
        }

        Comparable max = getMaximum();
        try {
            if (max != null && max.compareTo(value) < 0) {
                return false;
            }
        } catch (ClassCastException cce) {
            if (wantsCCE) {
                throw cce;
            }
            return false;
        }
        return true;
    
private voidreadObject(java.io.ObjectInputStream s)
Subclassed to update the internal representation of the mask after the default read operation has completed.

        s.defaultReadObject();
        updateMaskIfNecessary();
    
voidreplace(javax.swing.text.DocumentFilter$FilterBypass fb, int offset, int length, java.lang.String text, javax.swing.text.AttributeSet attrs)
Overriden to unconditionally allow the replace if ignoreDocumentMutate is true.

        if (ignoreDocumentMutate) {
            fb.replace(offset, length, text, attrs);
            return;
        }
        super.replace(fb, offset, length, text, attrs);
    
booleanreplace(ReplaceHolder rh)
When in !allowsInvalid mode the text is reset on every edit, thus supers implementation will position the cursor at the wrong position. As such, this invokes supers implementation and then invokes repositionCursor to correctly reset the cursor.

        int start = -1;
        int direction = 1;
        int literalCount = -1;

        if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
               (getFormattedTextField().getSelectionStart() != rh.offset ||
                   rh.length > 1)) {
            direction = -1;
        }
        if (!getAllowsInvalid()) {
            if ((rh.text == null || rh.text.length() == 0) && rh.length > 0) {
                // remove
                start = getFormattedTextField().getSelectionStart();
            }
            else {
                start = rh.offset;
            }
            literalCount = getLiteralCountTo(start);
        }
        if (super.replace(rh)) {
            if (start != -1) {
                int end = ((ExtendedReplaceHolder)rh).endOffset;

                end += ((ExtendedReplaceHolder)rh).endTextLength;
                repositionCursor(literalCount, end, direction);
            }
            else {
                start = ((ExtendedReplaceHolder)rh).endOffset;
                if (direction == 1) {
                    start += ((ExtendedReplaceHolder)rh).endTextLength;
                }
                repositionCursor(start, direction);
            }
            return true;
        }
        return false;
    
private voidrepositionCursor(int startLiteralCount, int end, int direction)
Repositions the cursor. startLiteralCount gives the number of literals to the start of the deleted range, end gives the ending location to adjust from, direction gives the direction relative to end to position the cursor from.

        int endLiteralCount = getLiteralCountTo(end);

        if (endLiteralCount != end) {
            end -= startLiteralCount;
            for (int counter = 0; counter < end; counter++) {
                if (isLiteral(counter)) {
                    end++;
                }
            }
        }
        repositionCursor(end, 1 /*direction*/);
    
voidresetValue(java.lang.Object value)
Resets the value of the JFormattedTextField to be value.

        Document doc = getFormattedTextField().getDocument();
        String string = valueToString(value);

        try {
            ignoreDocumentMutate = true;
            doc.remove(0, doc.getLength());
            doc.insertString(0, string, null);
        } finally {
            ignoreDocumentMutate = false;
        }
        updateValue(value);
    
voidselectField(java.lang.Object f, int count)
Selects the fields identified by attributes.

        AttributedCharacterIterator iterator = getIterator();

        if (iterator != null &&
                        (f instanceof AttributedCharacterIterator.Attribute)) {
            AttributedCharacterIterator.Attribute field =
                                   (AttributedCharacterIterator.Attribute)f;

            iterator.first();
            while (iterator.current() != CharacterIterator.DONE) {
                while (iterator.getAttribute(field) == null &&
                       iterator.next() != CharacterIterator.DONE);
                if (iterator.current() != CharacterIterator.DONE) {
                    int limit = iterator.getRunLimit(field);

                    if (--count <= 0) {
                        getFormattedTextField().select(iterator.getIndex(),
                                                       limit);
                        break;
                    }
                    iterator.setIndex(limit);
                    iterator.next();
                }
            }
        }
    
public voidsetFormat(java.text.Format 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

        this.format = format;
    
public voidsetMaximum(java.lang.Comparable max)
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

        if (getValueClass() == null && max != null) {
            setValueClass(max.getClass());
        }
        this.max = max;
    
public voidsetMinimum(java.lang.Comparable minimum)
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

        if (getValueClass() == null && minimum != null) {
            setValueClass(minimum.getClass());
        }
        min = minimum;
    
public java.lang.ObjectstringToValue(java.lang.String text)
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

        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;
    
java.lang.ObjectstringToValue(java.lang.String text, java.text.Format f)
Invokes parseObject on f, returning its value.

        if (f == null) {
            return text;
        }
        return f.parseObject(text);
    
voidupdateMask()
Updates the AttributedCharacterIterator by invoking formatToCharacterIterator on the Format. If this is successful, updateMask(AttributedCharacterIterator) is then invoked to update the internal bitmask.

        if (getFormat() != null) {
            Document doc = getFormattedTextField().getDocument();

            validMask = false;
            if (doc != null) {
                try {
                    string = doc.getText(0, doc.getLength());
                } catch (BadLocationException ble) {
                    string = null;
                }
                if (string != null) {
                    try {
                        Object value = stringToValue(string);
                        AttributedCharacterIterator iterator = getFormat().
                                  formatToCharacterIterator(value);

                        updateMask(iterator);
                    }
                    catch (ParseException pe) {}
                    catch (IllegalArgumentException iae) {}
                    catch (NullPointerException npe) {}
                }
            }
        }
    
private voidupdateMask(java.text.AttributedCharacterIterator iterator)
Updates the interal bitset from iterator. This will set validMask to true if iterator is non-null.

        if (iterator != null) {
            validMask = true;
            this.iterator = iterator;

            // Update the literal mask
            if (literalMask == null) {
                literalMask = new BitSet();
            }
            else {
                for (int counter = literalMask.length() - 1; counter >= 0;
                     counter--) {
                    literalMask.clear(counter);
                }
            }

            iterator.first();
            while (iterator.current() != CharacterIterator.DONE) {
                Map attributes = iterator.getAttributes();
                boolean set = isLiteral(attributes);
                int start = iterator.getIndex();
                int end = iterator.getRunLimit();

                while (start < end) {
                    if (set) {
                        literalMask.set(start);
                    }
                    else {
                        literalMask.clear(start);
                    }
                    start++;
                }
                iterator.setIndex(start);
            }
        }
    
voidupdateMaskIfNecessary()
Updates the AttributedCharacterIterator and bitset, if necessary.

        if (!getAllowsInvalid() && (getFormat() != null)) {
            if (!isValidMask()) {
                updateMask();
            }
            else {
                String newString = getFormattedTextField().getText();

                if (!newString.equals(string)) {
                    updateMask();
                }
            }
        }
    
voidupdateValue(java.lang.Object value)
Overriden to update the mask after invoking supers implementation.

        super.updateValue(value);
        updateMaskIfNecessary();
    
public java.lang.StringvalueToString(java.lang.Object value)
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

        if (value == null) {
            return "";
        }
        Format f = getFormat();

        if (f == null) {
            return value.toString();
        }
        return f.format(value);