comparison org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToNumberParser.d @ 78:0a55d2d5a946

Added file for databinding
author Frank Benoit <benoit@tionex.de>
date Tue, 14 Apr 2009 11:35:29 +0200
parents
children 6be48cf9f95c
comparison
equal deleted inserted replaced
76:f05e6e8b2f2d 78:0a55d2d5a946
1 /*******************************************************************************
2 * Copyright (c) 2007, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11
12 module org.eclipse.core.internal.databinding.conversion.StringToNumberParser;
13
14 import java.lang.all;
15
16 import java.math.BigDecimal;
17 import java.math.BigInteger;
18 import java.text.ParsePosition;
19
20 import org.eclipse.core.internal.databinding.BindingMessages;
21
22 import com.ibm.icu.text.NumberFormat;
23
24 /**
25 * Utility class for the parsing of strings to numbers.
26 *
27 * @since 1.0
28 */
29 public class StringToNumberParser {
30 private static final BigDecimal FLOAT_MAX_BIG_DECIMAL = new BigDecimal(
31 Float.MAX_VALUE);
32 private static final BigDecimal FLOAT_MIN_BIG_DECIMAL = new BigDecimal(
33 -Float.MAX_VALUE);
34
35 private static final BigDecimal DOUBLE_MAX_BIG_DECIMAL = new BigDecimal(
36 Double.MAX_VALUE);
37 private static final BigDecimal DOUBLE_MIN_BIG_DECIMAL = new BigDecimal(
38 -Double.MAX_VALUE);
39
40 /**
41 * @param value
42 * @param numberFormat
43 * @param primitive
44 * @return result
45 */
46 public static ParseResult parse(Object value, NumberFormat numberFormat,
47 bool primitive) {
48 if (!( null !is cast(String)value )) {
49 throw new IllegalArgumentException(
50 "Value to convert is not a String"); //$NON-NLS-1$
51 }
52
53 String source = cast(String) value;
54 ParseResult result = new ParseResult();
55 if (!primitive && source.trim().length() is 0) {
56 return result;
57 }
58
59 synchronized (numberFormat) {
60 ParsePosition position = new ParsePosition(0);
61 Number parseResult = null;
62 parseResult = numberFormat.parse(source, position);
63
64 if (position.getIndex() !is source.length()
65 || position.getErrorIndex() > -1) {
66
67 result.position = position;
68 } else {
69 result.number = parseResult;
70 }
71 }
72
73 return result;
74 }
75
76 /**
77 * The result of a parse operation.
78 *
79 * @since 1.0
80 */
81 public static class ParseResult {
82 /* package */Number number;
83 /* package */ParsePosition position;
84
85 /**
86 * The number as a result of the conversion. <code>null</code> if the
87 * value could not be converted or if the type is not a primitive and
88 * the value was an empty string.
89 *
90 * @return number
91 */
92 public Number getNumber() {
93 return number;
94 }
95
96 /**
97 * ParsePosition if an error occurred while parsing. <code>null</code>
98 * if no error occurred.
99 *
100 * @return parse position
101 */
102 public ParsePosition getPosition() {
103 return position;
104 }
105 }
106
107 /**
108 * Formats an appropriate message for a parsing error.
109 *
110 * @param value
111 * @param position
112 * @return message
113 */
114 public static String createParseErrorMessage(String value,
115 ParsePosition position) {
116 int errorIndex = (position.getErrorIndex() > -1) ? position
117 .getErrorIndex() : position.getIndex();
118
119 if (errorIndex < value.length()) {
120 return BindingMessages.formatString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR,
121 [cast(Object) stringcast(value), new Integer(errorIndex + 1),
122 new Character(value.charAt(errorIndex)) ]);
123 }
124 return BindingMessages.formatString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR_NO_CHARACTER,
125 [cast(Object) stringcast(value), new Integer(errorIndex + 1) ]);
126 }
127
128 /**
129 * Formats an appropriate message for an out of range error.
130 *
131 * @param minValue
132 * @param maxValue
133 * @param numberFormat when accessed method synchronizes on instance
134 * @return message
135 */
136 public static String createOutOfRangeMessage(Number minValue,
137 Number maxValue, NumberFormat numberFormat) {
138 String min = null;
139 String max = null;
140
141 synchronized (numberFormat) {
142 min = numberFormat.format(minValue);
143 max = numberFormat.format(maxValue);
144 }
145
146 return BindingMessages.formatString(
147 "Validate_NumberOutOfRangeError", [ cast(Object)min, max ]); //$NON-NLS-1$
148 }
149
150 /**
151 * Returns <code>true</code> if the provided <code>number</code> is in
152 * the range of a integer.
153 *
154 * @param number
155 * @return <code>true</code> if a valid integer
156 * @throws IllegalArgumentException
157 * if the number type is unsupported
158 */
159 public static bool inIntegerRange(Number number) {
160 return checkInteger(number, 31);
161 }
162
163 /**
164 * Validates the range of the provided <code>number</code>.
165 *
166 * @param number
167 * @param bitLength number of bits allowed to be in range
168 * @return <code>true</code> if in range
169 */
170 private static bool checkInteger(Number number, int bitLength) {
171 BigInteger bigInteger = null;
172
173 if ( null !is cast(Integer )number || null !is cast(Long)number ) {
174 bigInteger = BigInteger.valueOf(number.longValue());
175 } else if ( null !is cast(Float )number || null !is cast(Double)number ) {
176 double doubleValue = number.doubleValue();
177 /*
178 * doubleValue is doubleValue is used to check for NaN because NaN !is
179 * NaN. The only way to check for NaN is to compare that the value
180 * is equal to itself.
181 */
182 if (doubleValue is doubleValue
183 && doubleValue !is Double.NEGATIVE_INFINITY
184 && doubleValue !is Double.POSITIVE_INFINITY) {
185 bigInteger = (new BigDecimal(doubleValue)).toBigInteger();
186 } else {
187 return false;
188 }
189 } else if ( null !is cast(BigInteger)number ) {
190 bigInteger = cast(BigInteger) number;
191 } else if ( null !is cast(BigDecimal)number ) {
192 bigInteger = (cast(BigDecimal) number).toBigInteger();
193 } else {
194 /*
195 * The else is necessary as the ICU4J plugin has it's own BigDecimal
196 * implementation which isn't part of the replacement plugin. So
197 * that this will work we fall back on the double value of the
198 * number.
199 */
200 bigInteger = (new BigDecimal(number.doubleValue())).toBigInteger();
201 }
202
203 if (bigInteger !is null) {
204 return bigInteger.bitLength() <= bitLength;
205 }
206
207 throw new IllegalArgumentException(
208 "Number of type [" + number.getClass().getName() + "] is not supported."); //$NON-NLS-1$ //$NON-NLS-2$
209 }
210
211 /**
212 * Returns <code>true</code> if the provided <code>number</code> is in
213 * the range of a long.
214 *
215 * @param number
216 * @return <code>true</code> if in range
217 * @throws IllegalArgumentException
218 * if the number type is unsupported
219 */
220 public static bool inLongRange(Number number) {
221 return checkInteger(number, 63);
222 }
223
224 /**
225 * Returns <code>true</code> if the provided <code>number</code> is in
226 * the range of a float.
227 *
228 * @param number
229 * @return <code>true</code> if in range
230 * @throws IllegalArgumentException
231 * if the number type is unsupported
232 */
233 public static bool inFloatRange(Number number) {
234 return checkDecimal(number, FLOAT_MIN_BIG_DECIMAL, FLOAT_MAX_BIG_DECIMAL);
235 }
236
237 private static bool checkDecimal(Number number, BigDecimal min, BigDecimal max) {
238 BigDecimal bigDecimal = null;
239 if ( null !is cast(Integer )number || null !is cast(Long)number ) {
240 bigDecimal = new BigDecimal(number.doubleValue());
241 } else if ( null !is cast(Float )number || null !is cast(Double)number ) {
242 double doubleValue = number.doubleValue();
243
244 /*
245 * doubleValue is doubleValue is used to check for NaN because NaN !is
246 * NaN. The only way to check for NaN is to compare that the value
247 * is equal to itself.
248 */
249 if (doubleValue is doubleValue
250 && doubleValue !is Double.NEGATIVE_INFINITY
251 && doubleValue !is Double.POSITIVE_INFINITY) {
252 bigDecimal = new BigDecimal(doubleValue);
253 } else {
254 return false;
255 }
256 } else if ( null !is cast(BigInteger)number ) {
257 bigDecimal = new BigDecimal(cast(BigInteger) number);
258 } else if ( null !is cast(BigDecimal)number ) {
259 bigDecimal = cast(BigDecimal) number;
260 } else {
261 /*
262 * The else is necessary as the ICU4J plugin has it's own BigDecimal
263 * implementation which isn't part of the replacement plugin. So
264 * that this will work we fall back on the double value of the
265 * number.
266 */
267 bigDecimal = new BigDecimal(number.doubleValue());
268 }
269
270 if (bigDecimal !is null) {
271 return max.compareTo(bigDecimal) >= 0
272 && min.compareTo(bigDecimal) <= 0;
273 }
274
275 throw new IllegalArgumentException(
276 "Number of type [" + number.getClass().getName() + "] is not supported."); //$NON-NLS-1$ //$NON-NLS-2$
277 }
278
279 /**
280 * Returns <code>true</code> if the provided <code>number</code> is in
281 * the range of a double.
282 *
283 * @param number
284 * @return <code>true</code> if in range
285 * @throws IllegalArgumentException
286 * if the number type is unsupported
287 */
288 public static bool inDoubleRange(Number number) {
289 return checkDecimal(number, DOUBLE_MIN_BIG_DECIMAL, DOUBLE_MAX_BIG_DECIMAL);
290 }
291
292 /**
293 * Returns <code>true</code> if the provided <code>number</code> is in
294 * the range of a short.
295 *
296 * @param number
297 * @return <code>true</code> if in range
298 */
299 public static bool inShortRange(Number number) {
300 return checkInteger(number, 15);
301 }
302
303 /**
304 * Returns <code>true</code> if the provided <code>number</code> is in
305 * the range of a byte.
306 *
307 * @param number
308 * @return <code>true</code> if in range
309 */
310 public static bool inByteRange(Number number) {
311 return checkInteger(number, 7);
312 }
313 }