Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: ba506666f1a861fd85bdb846b95318afc6628b4f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*******************************************************************************
 * Copyright (c) 2007, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.core.internal.databinding.conversion;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.Format;
import java.text.ParsePosition;

import org.eclipse.core.internal.databinding.BindingMessages;

import com.ibm.icu.text.NumberFormat;

/**
 * Utility class for the parsing of strings to numbers.
 *
 * @since 1.0
 */
public class StringToNumberParser {
	private static final BigDecimal FLOAT_MAX_BIG_DECIMAL = new BigDecimal(
			Float.MAX_VALUE);
	private static final BigDecimal FLOAT_MIN_BIG_DECIMAL = new BigDecimal(
			-Float.MAX_VALUE);

	private static final BigDecimal DOUBLE_MAX_BIG_DECIMAL = new BigDecimal(
			Double.MAX_VALUE);
	private static final BigDecimal DOUBLE_MIN_BIG_DECIMAL = new BigDecimal(
			-Double.MAX_VALUE);

	/**
	 * @param value
	 * @param numberFormat
	 * @param primitive
	 * @return result
	 */
	public static ParseResult parse(Object value, NumberFormat numberFormat,
			boolean primitive) {
		if (!(value instanceof String)) {
			throw new IllegalArgumentException(
					"Value to convert is not a String"); //$NON-NLS-1$
		}

		String source = (String) value;
		ParseResult result = new ParseResult();
		if (!primitive && source.trim().length() == 0) {
			return result;
		}

		synchronized (numberFormat) {
			ParsePosition position = new ParsePosition(0);
			Number parseResult = null;
			parseResult = numberFormat.parse(source, position);

			if (position.getIndex() != source.length()
					|| position.getErrorIndex() > -1) {

				result.position = position;
			} else {
				result.number = parseResult;
			}
		}

		return result;
	}

	/**
	 * The result of a parse operation.
	 *
	 * @since 1.0
	 */
	public static class ParseResult {
		/* package */Number number;
		/* package */ParsePosition position;

		/**
		 * The number as a result of the conversion. <code>null</code> if the
		 * value could not be converted or if the type is not a primitive and
		 * the value was an empty string.
		 *
		 * @return number
		 */
		public Number getNumber() {
			return number;
		}

		/**
		 * ParsePosition if an error occurred while parsing. <code>null</code>
		 * if no error occurred.
		 *
		 * @return parse position
		 */
		public ParsePosition getPosition() {
			return position;
		}
	}

	/**
	 * Formats an appropriate message for a parsing error.
	 *
	 * @param value
	 * @param position
	 * @return message
	 */
	public static String createParseErrorMessage(String value,
			ParsePosition position) {
		int errorIndex = (position.getErrorIndex() > -1) ? position
				.getErrorIndex() : position.getIndex();

		if (errorIndex < value.length()) {
			return BindingMessages.formatString(
					BindingMessages.VALIDATE_NUMBER_PARSE_ERROR, new Object[] {
							value, Integer.valueOf(errorIndex + 1),
							Character.valueOf(value.charAt(errorIndex)) });
		}
		return BindingMessages.formatString(
				BindingMessages.VALIDATE_NUMBER_PARSE_ERROR_NO_CHARACTER,
				new Object[] { value, Integer.valueOf(errorIndex + 1) });
	}

	/**
	 * Formats an appropriate message for an out of range error.
	 *
	 * @param minValue
	 * @param maxValue
	 * @param numberFormat
	 *            when accessed method synchronizes on instance
	 * @return message
	 */
	public static String createOutOfRangeMessage(Number minValue,
			Number maxValue, Format numberFormat) {
		String min = null;
		String max = null;

		synchronized (numberFormat) {
			min = numberFormat.format(minValue);
			max = numberFormat.format(maxValue);
		}

		return BindingMessages.formatString(
				"Validate_NumberOutOfRangeError", new Object[] { min, max }); //$NON-NLS-1$
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a integer.
	 *
	 * @param number
	 * @return <code>true</code> if a valid integer
	 * @throws IllegalArgumentException
	 *             if the number type is unsupported
	 */
	public static boolean inIntegerRange(Number number) {
		return checkInteger(number, 31);
	}

	/**
	 * Validates the range of the provided <code>number</code>.
	 *
	 * @param number
	 * @param bitLength
	 *            number of bits allowed to be in range
	 * @return <code>true</code> if in range
	 */
	private static boolean checkInteger(Number number, int bitLength) {
		BigInteger bigInteger = null;

		if (number instanceof Integer || number instanceof Long) {
			bigInteger = BigInteger.valueOf(number.longValue());
		} else if (number instanceof Float || number instanceof Double) {
			double doubleValue = number.doubleValue();
			if (!Double.isNaN(doubleValue) && !Double.isInfinite(doubleValue)) {
				bigInteger = new BigDecimal(doubleValue).toBigInteger();
			} else {
				return false;
			}
		} else if (number instanceof BigInteger) {
			bigInteger = (BigInteger) number;
		} else if (number instanceof BigDecimal) {
			bigInteger = ((BigDecimal) number).toBigInteger();
		} else {
			/*
			 * The else is necessary as the ICU4J plugin has it's own BigDecimal
			 * implementation which isn't part of the replacement plugin. So
			 * that this will work we fall back on the double value of the
			 * number.
			 */
			bigInteger = new BigDecimal(number.doubleValue()).toBigInteger();
		}

		if (bigInteger != null) {
			return bigInteger.bitLength() <= bitLength;
		}

		throw new IllegalArgumentException(
				"Number of type [" + number.getClass().getName() + "] is not supported."); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a long.
	 *
	 * @param number
	 * @return <code>true</code> if in range
	 * @throws IllegalArgumentException
	 *             if the number type is unsupported
	 */
	public static boolean inLongRange(Number number) {
		return checkInteger(number, 63);
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a float.
	 *
	 * @param number
	 * @return <code>true</code> if in range
	 * @throws IllegalArgumentException
	 *             if the number type is unsupported
	 */
	public static boolean inFloatRange(Number number) {
		return checkDecimal(number, FLOAT_MIN_BIG_DECIMAL,
				FLOAT_MAX_BIG_DECIMAL);
	}

	private static boolean checkDecimal(Number number, BigDecimal min,
			BigDecimal max) {
		BigDecimal bigDecimal = null;
		if (number instanceof Integer || number instanceof Long) {
			bigDecimal = new BigDecimal(number.doubleValue());
		} else if (number instanceof Float || number instanceof Double) {
			double doubleValue = number.doubleValue();

			if (!Double.isNaN(doubleValue) && !Double.isInfinite(doubleValue)) {
				bigDecimal = new BigDecimal(doubleValue);
			} else {
				return false;
			}
		} else if (number instanceof BigInteger) {
			bigDecimal = new BigDecimal((BigInteger) number);
		} else if (number instanceof BigDecimal) {
			bigDecimal = (BigDecimal) number;
		} else {
			/*
			 * The else is necessary as the ICU4J plugin has it's own BigDecimal
			 * implementation which isn't part of the replacement plugin. So
			 * that this will work we fall back on the double value of the
			 * number.
			 */
			// if this is ever taken out, take care to un-comment the throw
			// clause and the if condition below, they were commented because
			// the
			// compiler complained about dead code..
			double doubleValue = number.doubleValue();

			if (!Double.isNaN(doubleValue) && !Double.isInfinite(doubleValue)) {
				bigDecimal = new BigDecimal(doubleValue);
			} else {
				return false;
			}
		}

		/* if (bigDecimal != null) */{
			return max.compareTo(bigDecimal) >= 0
					&& min.compareTo(bigDecimal) <= 0;
		}

		// throw new IllegalArgumentException(
		//				"Number of type [" + number.getClass().getName() + "] is not supported."); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a double.
	 *
	 * @param number
	 * @return <code>true</code> if in range
	 * @throws IllegalArgumentException
	 *             if the number type is unsupported
	 */
	public static boolean inDoubleRange(Number number) {
		return checkDecimal(number, DOUBLE_MIN_BIG_DECIMAL,
				DOUBLE_MAX_BIG_DECIMAL);
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a short.
	 *
	 * @param number
	 * @return <code>true</code> if in range
	 */
	public static boolean inShortRange(Number number) {
		return checkInteger(number, 15);
	}

	/**
	 * Returns <code>true</code> if the provided <code>number</code> is in the
	 * range of a byte.
	 *
	 * @param number
	 * @return <code>true</code> if in range
	 */
	public static boolean inByteRange(Number number) {
		return checkInteger(number, 7);
	}
}

Back to the top