466 lines · 16.7 KB · Java
RawDownload
/* * Copyright (c) 2000, 2010, 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.lang.reflect.*;import java.text.*;import java.util.*; /** * <code>NumberFormatter</code> subclasses <code>InternationalFormatter</code> * adding special behavior for numbers. Among the specializations are * (these are only used if the <code>NumberFormatter</code> does not display * invalid nubers, eg <code>setAllowsInvalid(false)</code>): * <ul> *   <li>Pressing +/- (- is determined from the *       <code>DecimalFormatSymbols</code> associated with the *       <code>DecimalFormat</code>) in any field but the exponent *       field will attempt to change the sign of the number to *       positive/negative. *   <li>Pressing +/- (- is determined from the *       <code>DecimalFormatSymbols</code> associated with the *       <code>DecimalFormat</code>) in the exponent field will *       attemp to change the sign of the exponent to positive/negative. * </ul> * <p> * If you are displaying scientific numbers, you may wish to turn on * overwrite mode, <code>setOverwriteMode(true)</code>. For example: * <pre> * DecimalFormat decimalFormat = new DecimalFormat("0.000E0"); * NumberFormatter textFormatter = new NumberFormatter(decimalFormat); * textFormatter.setOverwriteMode(true); * textFormatter.setAllowsInvalid(false); * </pre> * <p> * If you are going to allow the user to enter decimal * values, you should either force the DecimalFormat to contain at least * one decimal (<code>#.0###</code>), or allow the value to be invalid * <code>setAllowsInvalid(true)</code>. Otherwise users may not be able to * input decimal values. * <p> * <code>NumberFormatter</code> provides slightly different behavior to * <code>stringToValue</code> than that of its superclass. If you have * specified a Class for values, {@link #setValueClass}, that is one of * of <code>Integer</code>, <code>Long</code>, <code>Float</code>, * <code>Double</code>, <code>Byte</code> or <code>Short</code> and * the Format's <code>parseObject</code> returns an instance of * <code>Number</code>, the corresponding instance of the value class * will be created using the constructor appropriate for the primitive * type the value class represents. For example: * <code>setValueClass(Integer.class)</code> will cause the resulting * value to be created via * <code>new Integer(((Number)formatter.parseObject(string)).intValue())</code>. * This is typically useful if you * wish to set a min/max value as the various <code>Number</code> * implementations are generally not comparable to each other. This is also * useful if for some reason you need a specific <code>Number</code> * implementation for your values. * <p> * <strong>Warning:</strong> * 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<sup><font size="-2">TM</font></sup> * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @since 1.4 */public class NumberFormatter extends InternationalFormatter {    /** The special characters from the Format instance. */    private String specialChars;     /**     * Creates a <code>NumberFormatter</code> with the a default     * <code>NumberFormat</code> instance obtained from     * <code>NumberFormat.getNumberInstance()</code>.     */    public NumberFormatter() {        this(NumberFormat.getNumberInstance());    }     /**     * Creates a NumberFormatter with the specified Format instance.     *     * @param format Format used to dictate legal values     */    public NumberFormatter(NumberFormat format) {        super(format);        setFormat(format);        setAllowsInvalid(true);        setCommitsOnValidEdit(false);        setOverwriteMode(false);    }     /**     * Sets the format that dictates the legal values that can be edited     * and displayed.     * <p>     * If you have used the nullary constructor the value of this property     * will be determined for the current locale by way of the     * <code>NumberFormat.getNumberInstance()</code> method.     *     * @param format NumberFormat instance used to dictate legal values     */    public void setFormat(Format format) {        super.setFormat(format);         DecimalFormatSymbols dfs = getDecimalFormatSymbols();         if (dfs != null) {            StringBuilder sb = new StringBuilder();             sb.append(dfs.getCurrencySymbol());            sb.append(dfs.getDecimalSeparator());            sb.append(dfs.getGroupingSeparator());            sb.append(dfs.getInfinity());            sb.append(dfs.getInternationalCurrencySymbol());            sb.append(dfs.getMinusSign());            sb.append(dfs.getMonetaryDecimalSeparator());            sb.append(dfs.getNaN());            sb.append(dfs.getPercent());            sb.append('+');            specialChars = sb.toString();        }        else {            specialChars = "";        }    }     /**     * Invokes <code>parseObject</code> on <code>f</code>, returning     * its value.     */    Object stringToValue(String text, Format f) throws ParseException {        if (f == null) {            return text;        }        Object value = f.parseObject(text);         return convertValueToValueClass(value, getValueClass());    }     /**     * Converts the passed in value to the passed in class. This only     * works if <code>valueClass</code> is one of <code>Integer</code>,     * <code>Long</code>, <code>Float</code>, <code>Double</code>,     * <code>Byte</code> or <code>Short</code> and <code>value</code>     * is an instanceof <code>Number</code>.     */    private Object convertValueToValueClass(Object value, Class valueClass) {        if (valueClass != null && (value instanceof Number)) {            Number numberValue = (Number)value;            if (valueClass == Integer.class) {                return Integer.valueOf(numberValue.intValue());            }            else if (valueClass == Long.class) {                return Long.valueOf(numberValue.longValue());            }            else if (valueClass == Float.class) {                return Float.valueOf(numberValue.floatValue());            }            else if (valueClass == Double.class) {                return Double.valueOf(numberValue.doubleValue());            }            else if (valueClass == Byte.class) {                return Byte.valueOf(numberValue.byteValue());            }            else if (valueClass == Short.class) {                return Short.valueOf(numberValue.shortValue());            }        }        return value;    }     /**     * Returns the character that is used to toggle to positive values.     */    private char getPositiveSign() {        return '+';    }     /**     * Returns the character that is used to toggle to negative values.     */    private char getMinusSign() {        DecimalFormatSymbols dfs = getDecimalFormatSymbols();         if (dfs != null) {            return dfs.getMinusSign();        }        return '-';    }     /**     * Returns the character that is used to toggle to negative values.     */    private char getDecimalSeparator() {        DecimalFormatSymbols dfs = getDecimalFormatSymbols();         if (dfs != null) {            return dfs.getDecimalSeparator();        }        return '.';    }     /**     * Returns the DecimalFormatSymbols from the Format instance.     */    private DecimalFormatSymbols getDecimalFormatSymbols() {        Format f = getFormat();         if (f instanceof DecimalFormat) {            return ((DecimalFormat)f).getDecimalFormatSymbols();        }        return null;    }     /**     * Subclassed to return false if <code>text</code> contains in an invalid     * character to insert, that is, it is not a digit     * (<code>Character.isDigit()</code>) and     * not one of the characters defined by the DecimalFormatSymbols.     */    boolean isLegalInsertText(String text) {        if (getAllowsInvalid()) {            return true;        }        for (int counter = text.length() - 1; counter >= 0; counter--) {            char aChar = text.charAt(counter);             if (!Character.isDigit(aChar) &&                           specialChars.indexOf(aChar) == -1){                return false;            }        }        return true;    }     /**     * Subclassed to treat the decimal separator, grouping separator,     * exponent symbol, percent, permille, currency and sign as literals.     */    boolean isLiteral(Map attrs) {        if (!super.isLiteral(attrs)) {            if (attrs == null) {                return false;            }            int size = attrs.size();             if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {                size--;                if (attrs.get(NumberFormat.Field.INTEGER) != null) {                    size--;                }            }            if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {                size--;            }            if (attrs.get(NumberFormat.Field.PERCENT) != null) {                size--;            }            if (attrs.get(NumberFormat.Field.PERMILLE) != null) {                size--;            }            if (attrs.get(NumberFormat.Field.CURRENCY) != null) {                size--;            }            if (attrs.get(NumberFormat.Field.SIGN) != null) {                size--;            }            return size == 0;        }        return true;    }     /**     * Subclassed to make the decimal separator navigatable, as well     * as making the character between the integer field and the next     * field navigatable.     */    boolean isNavigatable(int index) {        if (!super.isNavigatable(index)) {            // Don't skip the decimal, it causes wierd behavior            return getBufferedChar(index) == getDecimalSeparator();        }        return true;    }     /**     * Returns the first <code>NumberFormat.Field</code> starting     * <code>index</code> incrementing by <code>direction</code>.     */    private NumberFormat.Field getFieldFrom(int index, int direction) {        if (isValidMask()) {            int max = getFormattedTextField().getDocument().getLength();            AttributedCharacterIterator iterator = getIterator();             if (index >= max) {                index += direction;            }            while (index >= 0 && index < max) {                iterator.setIndex(index);                 Map attrs = iterator.getAttributes();                 if (attrs != null && attrs.size() > 0) {                    for (Object key : attrs.keySet()) {                        if (key instanceof NumberFormat.Field) {                            return (NumberFormat.Field)key;                        }                    }                }                index += direction;            }        }        return null;    }     /**     * Overriden to toggle the value if the positive/minus sign     * is inserted.     */    void replace(DocumentFilter.FilterBypass fb, int offset, int length,                String string, AttributeSet attr) throws BadLocationException {        if (!getAllowsInvalid() && length == 0 && string != null &&            string.length() == 1 &&            toggleSignIfNecessary(fb, offset, string.charAt(0))) {            return;        }        super.replace(fb, offset, length, string, attr);    }     /**     * Will change the sign of the integer or exponent field if     * <code>aChar</code> is the positive or minus sign. Returns     * true if a sign change was attempted.     */    private boolean toggleSignIfNecessary(DocumentFilter.FilterBypass fb,                                              int offset, char aChar) throws                              BadLocationException {        if (aChar == getMinusSign() || aChar == getPositiveSign()) {            NumberFormat.Field field = getFieldFrom(offset, -1);            Object newValue;             try {                if (field == null ||                    (field != NumberFormat.Field.EXPONENT &&                     field != NumberFormat.Field.EXPONENT_SYMBOL &&                     field != NumberFormat.Field.EXPONENT_SIGN)) {                    newValue = toggleSign((aChar == getPositiveSign()));                }                else {                    // exponent                    newValue = toggleExponentSign(offset, aChar);                }                if (newValue != null && isValidValue(newValue, false)) {                    int lc = getLiteralCountTo(offset);                    String string = valueToString(newValue);                     fb.remove(0, fb.getDocument().getLength());                    fb.insertString(0, string, null);                    updateValue(newValue);                    repositionCursor(getLiteralCountTo(offset) -                                     lc + offset, 1);                    return true;                }            } catch (ParseException pe) {                invalidEdit();            }        }        return false;    }     /**     * Invoked to toggle the sign. For this to work the value class     * must have a single arg constructor that takes a String.     */    private Object toggleSign(boolean positive) throws ParseException {        Object value = stringToValue(getFormattedTextField().getText());         if (value != null) {            // toString isn't localized, so that using +/- should work            // correctly.            String string = value.toString();             if (string != null && string.length() > 0) {                if (positive) {                    if (string.charAt(0) == '-') {                        string = string.substring(1);                    }                }                else {                    if (string.charAt(0) == '+') {                        string = string.substring(1);                    }                    if (string.length() > 0 && string.charAt(0) != '-') {                        string = "-" + string;                    }                }                if (string != null) {                    Class<?> valueClass = getValueClass();                     if (valueClass == null) {                        valueClass = value.getClass();                    }                    try {                        Constructor cons = valueClass.getConstructor(                                              new Class[] { String.class });                         if (cons != null) {                            return cons.newInstance(new Object[]{string});                        }                    } catch (Throwable ex) { }                }            }        }        return null;    }     /**     * Invoked to toggle the sign of the exponent (for scientific     * numbers).     */    private Object toggleExponentSign(int offset, char aChar) throws                             BadLocationException, ParseException {        String string = getFormattedTextField().getText();        int replaceLength = 0;        int loc = getAttributeStart(NumberFormat.Field.EXPONENT_SIGN);         if (loc >= 0) {            replaceLength = 1;            offset = loc;        }        if (aChar == getPositiveSign()) {            string = getReplaceString(offset, replaceLength, null);        }        else {            string = getReplaceString(offset, replaceLength,                                      new String(new char[] { aChar }));        }        return stringToValue(string);    }}