-
Notifications
You must be signed in to change notification settings - Fork 197
/
AbstractConverter.java
477 lines (432 loc) · 16.7 KB
/
AbstractConverter.java
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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.beanutils.converters;
import java.lang.reflect.Array;
import java.util.Collection;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base {@link Converter} implementation that provides the structure
* for handling conversion <b>to</b> and <b>from</b> a specified type.
* <p>
* This implementation provides the basic structure for
* converting to/from a specified type optionally using a default
* value or throwing a {@link ConversionException} if a
* conversion error occurs.
* <p>
* Implementations should provide conversion to the specified
* type and from the specified type to a <code>String</code> value
* by implementing the following methods:
* <ul>
* <li><code>convertToString(value)</code> - convert to a String
* (default implementation uses the objects <code>toString()</code>
* method).</li>
* <li><code>convertToType(Class, value)</code> - convert
* to the specified type</li>
* </ul>
* <p>
* The default value has to be compliant to the default type of this
* converter - which is enforced by the generic type parameter. If a
* conversion is not possible and a default value is set, the converter
* tries to transform the default value to the requested target type.
* If this fails, a {@code ConversionException} if thrown.
*
* @version $Id$
* @since 1.8.0
*/
public abstract class AbstractConverter implements Converter {
/** Debug logging message to indicate default value configuration */
private static final String DEFAULT_CONFIG_MSG =
"(N.B. Converters can be configured to use default values to avoid throwing exceptions)";
/** Current package name */
// getPackage() below returns null on some platforms/jvm versions during the unit tests.
// private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + ".";
private static final String PACKAGE = "org.apache.commons.beanutils.converters.";
/**
* Logging for this instance.
*/
private transient Log log;
/**
* Should we return the default value on conversion errors?
*/
private boolean useDefault = false;
/**
* The default value specified to our Constructor, if any.
*/
private Object defaultValue = null;
// ----------------------------------------------------------- Constructors
/**
* Construct a <i>Converter</i> that throws a
* <code>ConversionException</code> if an error occurs.
*/
public AbstractConverter() {
}
/**
* Construct a <i>Converter</i> that returns a default
* value if an error occurs.
*
* @param defaultValue The default value to be returned
* if the value to be converted is missing or an error
* occurs converting the value.
*/
public AbstractConverter(final Object defaultValue) {
setDefaultValue(defaultValue);
}
// --------------------------------------------------------- Public Methods
/**
* Indicates whether a default value will be returned or exception
* thrown in the event of a conversion error.
*
* @return <code>true</code> if a default value will be returned for
* conversion errors or <code>false</code> if a {@link ConversionException}
* will be thrown.
*/
public boolean isUseDefault() {
return useDefault;
}
/**
* Convert the input object into an output object of the
* specified type.
*
* @param <T> the target type of the conversion
* @param type Data type to which this value should be converted
* @param value The input value to be converted
* @return The converted value.
* @throws ConversionException if conversion cannot be performed
* successfully and no default is specified.
*/
public <T> T convert(final Class<T> type, Object value) {
if (type == null) {
return convertToDefaultType(type, value);
}
Class<?> sourceType = value == null ? null : value.getClass();
final Class<T> targetType = ConvertUtils.primitiveToWrapper(type);
if (log().isDebugEnabled()) {
log().debug("Converting"
+ (value == null ? "" : " '" + toString(sourceType) + "'")
+ " value '" + value + "' to type '" + toString(targetType) + "'");
}
value = convertArray(value);
// Missing Value
if (value == null) {
return handleMissing(targetType);
}
sourceType = value.getClass();
try {
// Convert --> String
if (targetType.equals(String.class)) {
return targetType.cast(convertToString(value));
// No conversion necessary
} else if (targetType.equals(sourceType)) {
if (log().isDebugEnabled()) {
log().debug(" No conversion required, value is already a "
+ toString(targetType));
}
return targetType.cast(value);
// Convert --> Type
} else {
final Object result = convertToType(targetType, value);
if (log().isDebugEnabled()) {
log().debug(" Converted to " + toString(targetType) +
" value '" + result + "'");
}
return targetType.cast(result);
}
} catch (final Throwable t) {
return handleError(targetType, value, t);
}
}
/**
* Convert the input object into a String.
* <p>
* <b>N.B.</b>This implementation simply uses the value's
* <code>toString()</code> method and should be overriden if a
* more sophisticated mechanism for <i>conversion to a String</i>
* is required.
*
* @param value The input value to be converted.
* @return the converted String value.
* @throws Throwable if an error occurs converting to a String
*/
protected String convertToString(final Object value) throws Throwable {
return value.toString();
}
/**
* Convert the input object into an output object of the
* specified type.
* <p>
* Typical implementations will provide a minimum of
* <code>String --> type</code> conversion.
*
* @param <T> Target type of the conversion.
* @param type Data type to which this value should be converted.
* @param value The input value to be converted.
* @return The converted value.
* @throws Throwable if an error occurs converting to the specified type
*/
protected abstract <T> T convertToType(Class<T> type, Object value) throws Throwable;
/**
* Return the first element from an Array (or Collection)
* or the value unchanged if not an Array (or Collection).
*
* N.B. This needs to be overriden for array/Collection converters.
*
* @param value The value to convert
* @return The first element in an Array (or Collection)
* or the value unchanged if not an Array (or Collection)
*/
protected Object convertArray(final Object value) {
if (value == null) {
return null;
}
if (value.getClass().isArray()) {
if (Array.getLength(value) > 0) {
return Array.get(value, 0);
} else {
return null;
}
}
if (value instanceof Collection) {
final Collection<?> collection = (Collection<?>)value;
if (collection.size() > 0) {
return collection.iterator().next();
} else {
return null;
}
}
return value;
}
/**
* Handle Conversion Errors.
* <p>
* If a default value has been specified then it is returned
* otherwise a ConversionException is thrown.
*
* @param <T> Target type of the conversion.
* @param type Data type to which this value should be converted.
* @param value The input value to be converted
* @param cause The exception thrown by the <code>convert</code> method
* @return The default value.
* @throws ConversionException if no default value has been
* specified for this {@link Converter}.
*/
protected <T> T handleError(final Class<T> type, final Object value, final Throwable cause) {
if (log().isDebugEnabled()) {
if (cause instanceof ConversionException) {
log().debug(" Conversion threw ConversionException: " + cause.getMessage());
} else {
log().debug(" Conversion threw " + cause);
}
}
if (useDefault) {
return handleMissing(type);
}
ConversionException cex = null;
if (cause instanceof ConversionException) {
cex = (ConversionException)cause;
if (log().isDebugEnabled()) {
log().debug(" Re-throwing ConversionException: " + cex.getMessage());
log().debug(" " + DEFAULT_CONFIG_MSG);
}
} else {
final String msg = "Error converting from '" + toString(value.getClass()) +
"' to '" + toString(type) + "' " + cause.getMessage();
cex = new ConversionException(msg, cause);
if (log().isDebugEnabled()) {
log().debug(" Throwing ConversionException: " + msg);
log().debug(" " + DEFAULT_CONFIG_MSG);
}
BeanUtils.initCause(cex, cause);
}
throw cex;
}
/**
* Handle missing values.
* <p>
* If a default value has been specified, then it is returned (after a cast
* to the desired target class); otherwise a ConversionException is thrown.
*
* @param <T> the desired target type
* @param type Data type to which this value should be converted.
* @return The default value.
* @throws ConversionException if no default value has been
* specified for this {@link Converter}.
*/
protected <T> T handleMissing(final Class<T> type) {
if (useDefault || type.equals(String.class)) {
Object value = getDefault(type);
if (useDefault && value != null && !(type.equals(value.getClass()))) {
try {
value = convertToType(type, defaultValue);
} catch (final Throwable t) {
throw new ConversionException("Default conversion to " + toString(type)
+ " failed.", t);
}
}
if (log().isDebugEnabled()) {
log().debug(" Using default "
+ (value == null ? "" : toString(value.getClass()) + " ")
+ "value '" + defaultValue + "'");
}
// value is now either null or of the desired target type
return type.cast(value);
}
final ConversionException cex = new ConversionException("No value specified for '" +
toString(type) + "'");
if (log().isDebugEnabled()) {
log().debug(" Throwing ConversionException: " + cex.getMessage());
log().debug(" " + DEFAULT_CONFIG_MSG);
}
throw cex;
}
/**
* Set the default value, converting as required.
* <p>
* If the default value is different from the type the
* <code>Converter</code> handles, it will be converted
* to the handled type.
*
* @param defaultValue The default value to be returned
* if the value to be converted is missing or an error
* occurs converting the value.
* @throws ConversionException if an error occurs converting
* the default value
*/
protected void setDefaultValue(final Object defaultValue) {
useDefault = false;
if (log().isDebugEnabled()) {
log().debug("Setting default value: " + defaultValue);
}
if (defaultValue == null) {
this.defaultValue = null;
} else {
this.defaultValue = convert(getDefaultType(), defaultValue);
}
useDefault = true;
}
/**
* Return the default type this <code>Converter</code> handles.
*
* @return The default type this <code>Converter</code> handles.
*/
protected abstract Class<?> getDefaultType();
/**
* Return the default value for conversions to the specified
* type.
* @param type Data type to which this value should be converted.
* @return The default value for the specified type.
*/
protected Object getDefault(final Class<?> type) {
if (type.equals(String.class)) {
return null;
} else {
return defaultValue;
}
}
/**
* Provide a String representation of this converter.
*
* @return A String representation of this converter
*/
@Override
public String toString() {
return toString(getClass()) + "[UseDefault=" + useDefault + "]";
}
// ----------------------------------------------------------- Package Methods
/**
* Accessor method for Log instance.
* <p>
* The Log instance variable is transient and
* accessing it through this method ensures it
* is re-initialized when this instance is
* de-serialized.
*
* @return The Log instance.
*/
Log log() {
if (log == null) {
log = LogFactory.getLog(getClass());
}
return log;
}
/**
* Provide a String representation of a <code>java.lang.Class</code>.
* @param type The <code>java.lang.Class</code>.
* @return The String representation.
*/
String toString(final Class<?> type) {
String typeName = null;
if (type == null) {
typeName = "null";
} else if (type.isArray()) {
Class<?> elementType = type.getComponentType();
int count = 1;
while (elementType.isArray()) {
elementType = elementType .getComponentType();
count++;
}
typeName = elementType.getName();
for (int i = 0; i < count; i++) {
typeName += "[]";
}
} else {
typeName = type.getName();
}
if (typeName.startsWith("java.lang.") ||
typeName.startsWith("java.util.") ||
typeName.startsWith("java.math.")) {
typeName = typeName.substring("java.lang.".length());
} else if (typeName.startsWith(PACKAGE)) {
typeName = typeName.substring(PACKAGE.length());
}
return typeName;
}
/**
* Performs a conversion to the default type. This method is called if we do
* not have a target class. In this case, the T parameter is not set.
* Therefore, we can cast to it (which is required to fulfill the contract
* of the method signature).
*
* @param <T> the type of the result object
* @param targetClass the target class of the conversion
* @param value the value to be converted
* @return the converted value
*/
private <T> T convertToDefaultType(final Class<T> targetClass, final Object value) {
@SuppressWarnings("unchecked")
final
T result = (T) convert(getDefaultType(), value);
return result;
}
/**
* Generates a standard conversion exception with a message indicating that
* the passed in value cannot be converted to the desired target type.
*
* @param type the target type
* @param value the value to be converted
* @return a {@code ConversionException} with a standard message
* @since 1.9
*/
protected ConversionException conversionException(final Class<?> type, final Object value) {
return new ConversionException("Can't convert value '" + value
+ "' to type " + type);
}
}