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  */
  25 package javax.swing.text;
  26 
  27 import java.lang.reflect.*;
  28 import java.text.*;
  29 import java.util.*;
  30 import sun.reflect.misc.ReflectUtil;
  31 import sun.swing.SwingUtilities2;
  32 
  33 /**
  34  * <code>NumberFormatter</code> subclasses <code>InternationalFormatter</code>
  35  * adding special behavior for numbers. Among the specializations are
  36  * (these are only used if the <code>NumberFormatter</code> does not display
  37  * invalid numbers, for example, <code>setAllowsInvalid(false)</code>):
  38  * <ul>
  39  *   <li>Pressing +/- (- is determined from the
  40  *       <code>DecimalFormatSymbols</code> associated with the
  41  *       <code>DecimalFormat</code>) in any field but the exponent
  42  *       field will attempt to change the sign of the number to
  43  *       positive/negative.
  44  *   <li>Pressing +/- (- is determined from the
  45  *       <code>DecimalFormatSymbols</code> associated with the
  46  *       <code>DecimalFormat</code>) in the exponent field will
  47  *       attempt to change the sign of the exponent to positive/negative.
  48  * </ul>
  49  * <p>
  50  * If you are displaying scientific numbers, you may wish to turn on
  51  * overwrite mode, <code>setOverwriteMode(true)</code>. For example:
  52  * <pre>
  53  * DecimalFormat decimalFormat = new DecimalFormat("0.000E0");
  54  * NumberFormatter textFormatter = new NumberFormatter(decimalFormat);
  55  * textFormatter.setOverwriteMode(true);
  56  * textFormatter.setAllowsInvalid(false);
  57  * </pre>
  58  * <p>
  59  * If you are going to allow the user to enter decimal
  60  * values, you should either force the DecimalFormat to contain at least
  61  * one decimal (<code>#.0###</code>), or allow the value to be invalid
  62  * <code>setAllowsInvalid(true)</code>. Otherwise users may not be able to
  63  * input decimal values.
  64  * <p>
  65  * <code>NumberFormatter</code> provides slightly different behavior to
  66  * <code>stringToValue</code> than that of its superclass. If you have
  67  * specified a Class for values, {@link #setValueClass}, that is one of
  68  * of <code>Integer</code>, <code>Long</code>, <code>Float</code>,
  69  * <code>Double</code>, <code>Byte</code> or <code>Short</code> and
  70  * the Format's <code>parseObject</code> returns an instance of
  71  * <code>Number</code>, the corresponding instance of the value class
  72  * will be created using the constructor appropriate for the primitive
  73  * type the value class represents. For example:
  74  * <code>setValueClass(Integer.class)</code> will cause the resulting
  75  * value to be created via
  76  * <code>new Integer(((Number)formatter.parseObject(string)).intValue())</code>.
  77  * This is typically useful if you
  78  * wish to set a min/max value as the various <code>Number</code>
  79  * implementations are generally not comparable to each other. This is also
  80  * useful if for some reason you need a specific <code>Number</code>
  81  * implementation for your values.
  82  * <p>
  83  * <strong>Warning:</strong>
  84  * Serialized objects of this class will not be compatible with
  85  * future Swing releases. The current serialization support is
  86  * appropriate for short term storage or RMI between applications running
  87  * the same version of Swing.  As of 1.4, support for long term storage
  88  * of all JavaBeans&trade;
  89  * has been added to the <code>java.beans</code> package.
  90  * Please see {@link java.beans.XMLEncoder}.
  91  *
  92  * @since 1.4
  93  */
  94 @SuppressWarnings("serial") // Same-version serialization only
  95 public class NumberFormatter extends InternationalFormatter {
  96     /** The special characters from the Format instance. */
  97     private String specialChars;
  98 
  99     /**
 100      * Creates a <code>NumberFormatter</code> with the a default
 101      * <code>NumberFormat</code> instance obtained from
 102      * <code>NumberFormat.getNumberInstance()</code>.
 103      */
 104     public NumberFormatter() {
 105         this(NumberFormat.getNumberInstance());
 106     }
 107 
 108     /**
 109      * Creates a NumberFormatter with the specified Format instance.
 110      *
 111      * @param format Format used to dictate legal values
 112      */
 113     public NumberFormatter(NumberFormat format) {
 114         super(format);
 115         setFormat(format);
 116         setAllowsInvalid(true);
 117         setCommitsOnValidEdit(false);
 118         setOverwriteMode(false);
 119     }
 120 
 121     /**
 122      * Sets the format that dictates the legal values that can be edited
 123      * and displayed.
 124      * <p>
 125      * If you have used the nullary constructor the value of this property
 126      * will be determined for the current locale by way of the
 127      * <code>NumberFormat.getNumberInstance()</code> method.
 128      *
 129      * @param format NumberFormat instance used to dictate legal values
 130      */
 131     public void setFormat(Format format) {
 132         super.setFormat(format);
 133 
 134         DecimalFormatSymbols dfs = getDecimalFormatSymbols();
 135 
 136         if (dfs != null) {
 137             StringBuilder sb = new StringBuilder();
 138 
 139             sb.append(dfs.getCurrencySymbol());
 140             sb.append(dfs.getDecimalSeparator());
 141             sb.append(dfs.getGroupingSeparator());
 142             sb.append(dfs.getInfinity());
 143             sb.append(dfs.getInternationalCurrencySymbol());
 144             sb.append(dfs.getMinusSign());
 145             sb.append(dfs.getMonetaryDecimalSeparator());
 146             sb.append(dfs.getNaN());
 147             sb.append(dfs.getPercent());
 148             sb.append('+');
 149             specialChars = sb.toString();
 150         }
 151         else {
 152             specialChars = "";
 153         }
 154     }
 155 
 156     /**
 157      * Invokes <code>parseObject</code> on <code>f</code>, returning
 158      * its value.
 159      */
 160     Object stringToValue(String text, Format f) throws ParseException {
 161         if (f == null) {
 162             return text;
 163         }
 164         Object value = f.parseObject(text);
 165 
 166         return convertValueToValueClass(value, getValueClass());
 167     }
 168 
 169     /**
 170      * Converts the passed in value to the passed in class. This only
 171      * works if <code>valueClass</code> is one of <code>Integer</code>,
 172      * <code>Long</code>, <code>Float</code>, <code>Double</code>,
 173      * <code>Byte</code> or <code>Short</code> and <code>value</code>
 174      * is an instanceof <code>Number</code>.
 175      */
 176     private Object convertValueToValueClass(Object value,
 177                                             Class<?> valueClass) {
 178         if (valueClass != null && (value instanceof Number)) {
 179             Number numberValue = (Number)value;
 180             if (valueClass == Integer.class) {
 181                 return Integer.valueOf(numberValue.intValue());
 182             }
 183             else if (valueClass == Long.class) {
 184                 return Long.valueOf(numberValue.longValue());
 185             }
 186             else if (valueClass == Float.class) {
 187                 return Float.valueOf(numberValue.floatValue());
 188             }
 189             else if (valueClass == Double.class) {
 190                 return Double.valueOf(numberValue.doubleValue());
 191             }
 192             else if (valueClass == Byte.class) {
 193                 return Byte.valueOf(numberValue.byteValue());
 194             }
 195             else if (valueClass == Short.class) {
 196                 return Short.valueOf(numberValue.shortValue());
 197             }
 198         }
 199         return value;
 200     }
 201 
 202     /**
 203      * Returns the character that is used to toggle to positive values.
 204      */
 205     private char getPositiveSign() {
 206         return '+';
 207     }
 208 
 209     /**
 210      * Returns the character that is used to toggle to negative values.
 211      */
 212     private char getMinusSign() {
 213         DecimalFormatSymbols dfs = getDecimalFormatSymbols();
 214 
 215         if (dfs != null) {
 216             return dfs.getMinusSign();
 217         }
 218         return '-';
 219     }
 220 
 221     /**
 222      * Returns the character that is used to toggle to negative values.
 223      */
 224     private char getDecimalSeparator() {
 225         DecimalFormatSymbols dfs = getDecimalFormatSymbols();
 226 
 227         if (dfs != null) {
 228             return dfs.getDecimalSeparator();
 229         }
 230         return '.';
 231     }
 232 
 233     /**
 234      * Returns the DecimalFormatSymbols from the Format instance.
 235      */
 236     private DecimalFormatSymbols getDecimalFormatSymbols() {
 237         Format f = getFormat();
 238 
 239         if (f instanceof DecimalFormat) {
 240             return ((DecimalFormat)f).getDecimalFormatSymbols();
 241         }
 242         return null;
 243     }
 244 
 245     /**
 246      * Subclassed to return false if <code>text</code> contains in an invalid
 247      * character to insert, that is, it is not a digit
 248      * (<code>Character.isDigit()</code>) and
 249      * not one of the characters defined by the DecimalFormatSymbols.
 250      */
 251     boolean isLegalInsertText(String text) {
 252         if (getAllowsInvalid()) {
 253             return true;
 254         }
 255         for (int counter = text.length() - 1; counter >= 0; counter--) {
 256             char aChar = text.charAt(counter);
 257 
 258             if (!Character.isDigit(aChar) &&
 259                            specialChars.indexOf(aChar) == -1){
 260                 return false;
 261             }
 262         }
 263         return true;
 264     }
 265 
 266     /**
 267      * Subclassed to treat the decimal separator, grouping separator,
 268      * exponent symbol, percent, permille, currency and sign as literals.
 269      */
 270     boolean isLiteral(Map<?, ?> attrs) {
 271         if (!super.isLiteral(attrs)) {
 272             if (attrs == null) {
 273                 return false;
 274             }
 275             int size = attrs.size();
 276 
 277             if (attrs.get(NumberFormat.Field.GROUPING_SEPARATOR) != null) {
 278                 size--;
 279                 if (attrs.get(NumberFormat.Field.INTEGER) != null) {
 280                     size--;
 281                 }
 282             }
 283             if (attrs.get(NumberFormat.Field.EXPONENT_SYMBOL) != null) {
 284                 size--;
 285             }
 286             if (attrs.get(NumberFormat.Field.PERCENT) != null) {
 287                 size--;
 288             }
 289             if (attrs.get(NumberFormat.Field.PERMILLE) != null) {
 290                 size--;
 291             }
 292             if (attrs.get(NumberFormat.Field.CURRENCY) != null) {
 293                 size--;
 294             }
 295             if (attrs.get(NumberFormat.Field.SIGN) != null) {
 296                 size--;
 297             }
 298             return size == 0;
 299         }
 300         return true;
 301     }
 302 
 303     /**
 304      * Subclassed to make the decimal separator navigable, as well
 305      * as making the character between the integer field and the next
 306      * field navigable.
 307      */
 308     boolean isNavigatable(int index) {
 309         if (!super.isNavigatable(index)) {
 310             // Don't skip the decimal, it causes wierd behavior
 311             return getBufferedChar(index) == getDecimalSeparator();
 312         }
 313         return true;
 314     }
 315 
 316     /**
 317      * Returns the first <code>NumberFormat.Field</code> starting
 318      * <code>index</code> incrementing by <code>direction</code>.
 319      */
 320     private NumberFormat.Field getFieldFrom(int index, int direction) {
 321         if (isValidMask()) {
 322             int max = getFormattedTextField().getDocument().getLength();
 323             AttributedCharacterIterator iterator = getIterator();
 324 
 325             if (index >= max) {
 326                 index += direction;
 327             }
 328             while (index >= 0 && index < max) {
 329                 iterator.setIndex(index);
 330 
 331                 Map<?,?> attrs = iterator.getAttributes();
 332 
 333                 if (attrs != null && attrs.size() > 0) {
 334                     for (Object key : attrs.keySet()) {
 335                         if (key instanceof NumberFormat.Field) {
 336                             return (NumberFormat.Field)key;
 337                         }
 338                     }
 339                 }
 340                 index += direction;
 341             }
 342         }
 343         return null;
 344     }
 345 
 346     /**
 347      * Overriden to toggle the value if the positive/minus sign
 348      * is inserted.
 349      */
 350     void replace(DocumentFilter.FilterBypass fb, int offset, int length,
 351                 String string, AttributeSet attr) throws BadLocationException {
 352         if (!getAllowsInvalid() && length == 0 && string != null &&
 353             string.length() == 1 &&
 354             toggleSignIfNecessary(fb, offset, string.charAt(0))) {
 355             return;
 356         }
 357         super.replace(fb, offset, length, string, attr);
 358     }
 359 
 360     /**
 361      * Will change the sign of the integer or exponent field if
 362      * <code>aChar</code> is the positive or minus sign. Returns
 363      * true if a sign change was attempted.
 364      */
 365     private boolean toggleSignIfNecessary(DocumentFilter.FilterBypass fb,
 366                                               int offset, char aChar) throws
 367                               BadLocationException {
 368         if (aChar == getMinusSign() || aChar == getPositiveSign()) {
 369             NumberFormat.Field field = getFieldFrom(offset, -1);
 370             Object newValue;
 371 
 372             try {
 373                 if (field == null ||
 374                     (field != NumberFormat.Field.EXPONENT &&
 375                      field != NumberFormat.Field.EXPONENT_SYMBOL &&
 376                      field != NumberFormat.Field.EXPONENT_SIGN)) {
 377                     newValue = toggleSign((aChar == getPositiveSign()));
 378                 }
 379                 else {
 380                     // exponent
 381                     newValue = toggleExponentSign(offset, aChar);
 382                 }
 383                 if (newValue != null && isValidValue(newValue, false)) {
 384                     int lc = getLiteralCountTo(offset);
 385                     String string = valueToString(newValue);
 386 
 387                     fb.remove(0, fb.getDocument().getLength());
 388                     fb.insertString(0, string, null);
 389                     updateValue(newValue);
 390                     repositionCursor(getLiteralCountTo(offset) -
 391                                      lc + offset, 1);
 392                     return true;
 393                 }
 394             } catch (ParseException pe) {
 395                 invalidEdit();
 396             }
 397         }
 398         return false;
 399     }
 400 
 401     /**
 402      * Invoked to toggle the sign. For this to work the value class
 403      * must have a single arg constructor that takes a String.
 404      */
 405     private Object toggleSign(boolean positive) throws ParseException {
 406         Object value = stringToValue(getFormattedTextField().getText());
 407 
 408         if (value != null) {
 409             // toString isn't localized, so that using +/- should work
 410             // correctly.
 411             String string = value.toString();
 412 
 413             if (string != null && string.length() > 0) {
 414                 if (positive) {
 415                     if (string.charAt(0) == '-') {
 416                         string = string.substring(1);
 417                     }
 418                 }
 419                 else {
 420                     if (string.charAt(0) == '+') {
 421                         string = string.substring(1);
 422                     }
 423                     if (string.length() > 0 && string.charAt(0) != '-') {
 424                         string = "-" + string;
 425                     }
 426                 }
 427                 if (string != null) {
 428                     Class<?> valueClass = getValueClass();
 429 
 430                     if (valueClass == null) {
 431                         valueClass = value.getClass();
 432                     }
 433                     try {
 434                         ReflectUtil.checkPackageAccess(valueClass);
 435                         SwingUtilities2.checkAccess(valueClass.getModifiers());
 436                         Constructor<?> cons = valueClass.getConstructor(
 437                                               new Class<?>[] { String.class });
 438                         if (cons != null) {
 439                             SwingUtilities2.checkAccess(cons.getModifiers());
 440                             return cons.newInstance(new Object[]{string});
 441                         }
 442                     } catch (Throwable ex) { }
 443                 }
 444             }
 445         }
 446         return null;
 447     }
 448 
 449     /**
 450      * Invoked to toggle the sign of the exponent (for scientific
 451      * numbers).
 452      */
 453     private Object toggleExponentSign(int offset, char aChar) throws
 454                              BadLocationException, ParseException {
 455         String string = getFormattedTextField().getText();
 456         int replaceLength = 0;
 457         int loc = getAttributeStart(NumberFormat.Field.EXPONENT_SIGN);
 458 
 459         if (loc >= 0) {
 460             replaceLength = 1;
 461             offset = loc;
 462         }
 463         if (aChar == getPositiveSign()) {
 464             string = getReplaceString(offset, replaceLength, null);
 465         }
 466         else {
 467             string = getReplaceString(offset, replaceLength,
 468                                       new String(new char[] { aChar }));
 469         }
 470         return stringToValue(string);
 471     }
 472 }