001: /* NumberInputElement.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri May 4 11:39:46 2007, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul.impl;
020:
021: import java.math.BigDecimal;
022: import java.math.RoundingMode;
023: import java.text.NumberFormat;
024: import java.text.DecimalFormat;
025: import java.text.DecimalFormatSymbols;
026:
027: import org.zkoss.lang.JVMs;
028: import org.zkoss.util.Locales;
029: import org.zkoss.math.RoundingModes;
030:
031: /**
032: * A skeletal implementation for number-type input box.
033: *
034: * @author tomyeh
035: */
036: abstract public class NumberInputElement extends FormatInputElement {
037: /** The rounding mode. */
038: private int _rounding = BigDecimal.ROUND_HALF_EVEN;
039:
040: /** Sets the rounding mode.
041: * Note: You cannot change the rounding mode unless you are
042: * using Java 6 or later.
043: *
044: * @param mode the rounding mode. Allowed value:
045: * {@link BigDecimal#ROUND_CEILING}, {@link BigDecimal#ROUND_DOWN},
046: * {@link BigDecimal#ROUND_FLOOR}, {@link BigDecimal#ROUND_HALF_DOWN},
047: * {@link BigDecimal#ROUND_HALF_UP}, {@link BigDecimal#ROUND_HALF_EVEN},
048: * {@link BigDecimal#ROUND_UNNECESSARY} and {@link BigDecimal#ROUND_UP}
049: *
050: * @exception UnsupportedOperationException if Java 5 or below
051: */
052: public void setRoundingMode(int mode) {
053: if (_rounding != mode) {
054: if (!JVMs.isJava6())
055: throw new UnsupportedOperationException(
056: "Java 6 or above is required");
057: _rounding = mode;
058: }
059: }
060:
061: /** Sets the rounding mode by the name.
062: * Note: You cannot change the rounding mode unless you are
063: * using Java 6 or later.
064: *
065: * @param name the rounding mode's name. Allowed value:
066: <dl>
067: <dt>CEILING</dt>
068: <dd>Rounding mode to round towards positive infinity.</dd>
069: <dt>DOWN</dt>
070: <dd>Rounding mode to round towards zero.</dd>
071: <dt>FLOOR</dt>
072: <dd>Rounding mode to round towards negative infinity.</dd>
073: <dt>HALF_DOWN</dt>
074: <dd>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.</dd>
075: <dt>HALF_EVEN</dt>
076: <dd>Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.</dd>
077: <dt>HALF_UP</dt>
078: <dd>Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.</dd>
079: <dt>UNNECESSARY</dt>
080: <dd>Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.</dd>
081: <dt>UP</dt>
082: <dd>Rounding mode to round away from zero.</dd>
083: </dl>
084: * @exception UnsupportedOperationException if Java 5 or below
085: * @see RoundingModes
086: */
087: public void setRoundingMode(String name) {
088: setRoundingMode(RoundingModes.valueOf(name));
089: }
090:
091: /** Returns the rounding mode.
092: * <p>Default: {@link BigDecimal#ROUND_HALF_EVEN}.
093: */
094: public int getRoundingMode() {
095: return _rounding;
096: }
097:
098: //utilities//
099: /** Formats a number (Integer, BigDecimal...) into a string.
100: * If null, an empty string is returned.
101: *
102: * <p>A utility to assist the handling of numeric data.
103: *
104: * @see #toNumberOnly
105: * @param defaultFormat used if {@link #getFormat} returns null.
106: * If defaultFormat and {@link #getFormat} are both null,
107: * the system's default format is used.
108: */
109: protected String formatNumber(Object value, String defaultFormat) {
110: if (value == null)
111: return "";
112:
113: final DecimalFormat df = (DecimalFormat) NumberFormat
114: .getInstance(Locales.getCurrent());
115: if (_rounding != BigDecimal.ROUND_HALF_EVEN)
116: df.setRoundingMode(RoundingMode.valueOf(_rounding));
117:
118: String fmt = getFormat();
119: if (fmt == null)
120: fmt = defaultFormat;
121: if (fmt != null)
122: df.applyPattern(fmt);
123: return df.format(value);
124: }
125:
126: /** Filters out non digit characters, such comma and whitespace,
127: * from the specified value.
128: * It is designed to let user enter data in more free style.
129: * They may or may not enter data in the specified format.
130: *
131: * @return a two element array. The first element is the string to
132: * parse with, say, Double.parseDouble. The second element is
133: * an integer to indicate how many digits the result shall be scaled.
134: * For example, if the second element is 2. Then, the result shall be
135: * divided with 10 ^ 2.
136: *
137: * @see #formatNumber
138: */
139: protected Object[] toNumberOnly(String val) {
140: if (val == null)
141: return new Object[] { null, null };
142:
143: final DecimalFormatSymbols symbols = new DecimalFormatSymbols(
144: Locales.getCurrent());
145: final char GROUPING = symbols.getGroupingSeparator(), DECIMAL = symbols
146: .getDecimalSeparator(), PERCENT = symbols.getPercent(), PER_MILL = symbols
147: .getPerMill(), //1/1000
148: //not support yet: INFINITY = symbols.getInfinity(), NAN = symbols.getNaN(),
149: MINUS = symbols.getMinusSign();
150: final String fmt = getFormat();
151: StringBuffer sb = null;
152: int divscale = 0; //the second element
153: boolean minus = false;
154: for (int j = 0, len = val.length(); j < len; ++j) {
155: final char cc = val.charAt(j);
156:
157: boolean ignore = false;
158: //We handle percent and (nnn) specially
159: if (cc == PERCENT) {
160: divscale += 2;
161: ignore = true;
162: } else if (cc == PER_MILL) {
163: divscale += 3;
164: ignore = true;
165: } else if (cc == '(') {
166: minus = true;
167: ignore = true;
168: } else if (cc == '+') {
169: ignore = true;
170: }
171:
172: //We don't add if cc shall be ignored (not alphanum but in fmt)
173: if (!ignore)
174: ignore = (cc < '0' || cc > '9')
175: && cc != DECIMAL
176: && cc != MINUS
177: && cc != '+'
178: && (Character.isWhitespace(cc)
179: || cc == GROUPING || cc == ')' || (fmt != null && fmt
180: .indexOf(cc) >= 0));
181: if (ignore) {
182: if (sb == null)
183: sb = new StringBuffer(len).append(val.substring(0,
184: j));
185: } else {
186: final char c2 = cc == MINUS ? '-' : cc == DECIMAL ? '.'
187: : cc;
188: if (cc != c2) {
189: if (sb == null)
190: sb = new StringBuffer(len).append(val
191: .substring(0, j));
192: sb.append(c2);
193: } else if (sb != null) {
194: sb.append(c2);
195: }
196: }
197: }
198: if (minus) {
199: if (sb == null)
200: sb = new StringBuffer(val.length() + 1).append(val);
201: if (sb.length() > 0) {
202: if (sb.charAt(0) == '-') {
203: sb.deleteCharAt(0);
204: } else {
205: sb.insert(0, '-');
206: }
207: }
208: }
209: return new Object[] { (sb != null ? sb.toString() : val),
210: new Integer(divscale) };
211: }
212: }
|