1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.beanutils.converters;
18
19 import java.lang.reflect.Array;
20 import java.util.Collection;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.commons.beanutils.BeanUtils;
24 import org.apache.commons.beanutils.ConversionException;
25 import org.apache.commons.beanutils.Converter;
26
27 /***
28 * Base {@link Converter} implementation that provides the structure
29 * for handling conversion <b>to</b> and <b>from</b> a specified type.
30 * <p>
31 * This implementation provides the basic structure for
32 * converting to/from a specified type optionally using a default
33 * value or throwing a {@link ConversionException} if a
34 * conversion error occurs.
35 * <p>
36 * Implementations should provide conversion to the specified
37 * type and from the specified type to a <code>String</code> value
38 * by implementing the following methods:
39 * <ul>
40 * <li><code>convertToString(value)</code> - convert to a String
41 * (default implementation uses the objects <code>toString()</code>
42 * method).</li>
43 * <li><code>convertToType(Class, value)</code> - convert
44 * to the specified type</li>
45 * </ul>
46 *
47 * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
48 * @since 1.8.0
49 */
50 public abstract class AbstractConverter implements Converter {
51
52 /*** Debug logging message to indicate default value configuration */
53 private static final String DEFAULT_CONFIG_MSG =
54 "(N.B. Converters can be configured to use default values to avoid throwing exceptions)";
55
56 /**</package-summary/html">Current package name *//package-summary.html">em>* Current package name */
57
58
59 private static final String PACKAGE = "org.apache.commons.beanutils.converters.";
60
61 /***
62 * Logging for this instance.
63 */
64 private transient Log log;
65
66 /***
67 * The default type this <code>Converter</code> handles.
68 */
69 private Class defaultType = null;
70
71 /***
72 * Should we return the default value on conversion errors?
73 */
74 private boolean useDefault = false;
75
76 /***
77 * The default value specified to our Constructor, if any.
78 */
79 private Object defaultValue = null;
80
81
82
83 /***
84 * Construct a <i>Converter</i> that throws a
85 * <code>ConversionException</code> if an error occurs.
86 *
87 * @param defaultType The default type this <code>Converter</code>
88 * handles
89 */
90 public AbstractConverter(Class defaultType) {
91 this.defaultType = defaultType;
92 if (defaultType == null) {
93 throw new IllegalArgumentException("Default type is missing.");
94 }
95 }
96
97 /***
98 * Construct a <i>Converter</i> that returns a default
99 * value if an error occurs.
100 *
101 * @param defaultType The default type this <code>Converter</code>
102 * handles
103 * @param defaultValue The default value to be returned
104 * if the value to be converted is missing or an error
105 * occurs converting the value.
106 */
107 public AbstractConverter(Class defaultType, Object defaultValue) {
108 this(defaultType);
109 setDefaultValue(defaultValue);
110 }
111
112
113
114 /***
115 * Indicates whether a default value will be returned or exception
116 * thrown in the event of a conversion error.
117 *
118 * @return <code>true</code> if a default value will be returned for
119 * conversion errors or <code>false</code> if a {@link ConversionException}
120 * will be thrown.
121 */
122 public boolean isUseDefault() {
123 return useDefault;
124 }
125
126 /***
127 * Convert the input object into an output object of the
128 * specified type.
129 *
130 * @param type Data type to which this value should be converted
131 * @param value The input value to be converted
132 * @return The converted value.
133 * @throws ConversionException if conversion cannot be performed
134 * successfully and no default is specified.
135 */
136 public Object convert(Class type, Object value) {
137
138 Class sourceType = value == null ? null : value.getClass();
139 Class targetType = primitive(type == null ? getDefaultType() : type);
140
141 if (log().isDebugEnabled()) {
142 log().debug("Converting"
143 + (value == null ? "" : " '" + toString(sourceType) + "'")
144 + " value '" + value + "' to type '" + toString(targetType) + "'");
145 }
146
147 value = convertArray(value);
148
149
150 if (value == null) {
151 return handleMissing(targetType);
152 }
153
154 sourceType = value.getClass();
155
156 try {
157
158 if (targetType.equals(String.class)) {
159 return convertToString(value);
160
161
162 } else if (targetType.equals(sourceType)) {
163 if (log().isDebugEnabled()) {
164 log().debug(" No conversion required, value is already a "
165 + toString(targetType));
166 }
167 return value;
168
169
170 } else {
171 Object result = convertToType(targetType, value);
172 if (log().isDebugEnabled()) {
173 log().debug(" Converted to " + toString(targetType) +
174 " value '" + result + "'");
175 }
176 return result;
177 }
178 } catch (Throwable t) {
179 return handleError(targetType, value, t);
180 }
181
182 }
183
184 /***
185 * Convert the input object into a String.
186 * <p>
187 * <b>N.B.</b>This implementation simply uses the value's
188 * <code>toString()</code> method and should be overriden if a
189 * more sophisticated mechanism for <i>conversion to a String</i>
190 * is required.
191 *
192 * @param value The input value to be converted.
193 * @return the converted String value.
194 * @throws Throwable if an error occurs converting to a String
195 */
196 protected String convertToString(Object value) throws Throwable {
197 return value.toString();
198 }
199
200 /***
201 * Convert the input object into an output object of the
202 * specified type.
203 * <p>
204 * Typical implementations will provide a minimum of
205 * <code>String --> type</code> conversion.
206 *
207 * @param type Data type to which this value should be converted.
208 * @param value The input value to be converted.
209 * @return The converted value.
210 * @throws Throwable if an error occurs converting to the specified type
211 */
212 protected abstract Object convertToType(Class type, Object value) throws Throwable;
213
214 /***
215 * Return the first element from an Array (or Collection)
216 * or the value unchanged if not an Array (or Collection).
217 *
218 * N.B. This needs to be overriden for array/Collection converters.
219 *
220 * @param value The value to convert
221 * @return The first element in an Array (or Collection)
222 * or the value unchanged if not an Array (or Collection)
223 */
224 protected Object convertArray(Object value) {
225 if (value == null) {
226 return null;
227 }
228 if (value.getClass().isArray()) {
229 if (Array.getLength(value) > 0) {
230 return Array.get(value, 0);
231 } else {
232 return null;
233 }
234 }
235 if (value instanceof Collection) {
236 Collection collection = (Collection)value;
237 if (collection.size() > 0) {
238 return collection.iterator().next();
239 } else {
240 return null;
241 }
242 }
243 return value;
244 }
245
246 /***
247 * Handle Conversion Errors.
248 * <p>
249 * If a default value has been specified then it is returned
250 * otherwise a ConversionException is thrown.
251 *
252 * @param type Data type to which this value should be converted.
253 * @param value The input value to be converted
254 * @param cause The exception thrown by the <code>convert</code> method
255 * @return The default value.
256 * @throws ConversionException if no default value has been
257 * specified for this {@link Converter}.
258 */
259 protected Object handleError(Class type, Object value, Throwable cause) {
260 if (log().isDebugEnabled()) {
261 if (cause instanceof ConversionException) {
262 log().debug(" Conversion threw ConversionException: " + cause.getMessage());
263 } else {
264 log().debug(" Conversion threw " + cause);
265 }
266 }
267
268 if (useDefault) {
269 return handleMissing(type);
270 }
271
272 ConversionException cex = null;
273 if (cause instanceof ConversionException) {
274 cex = (ConversionException)cause;
275 if (log().isDebugEnabled()) {
276 log().debug(" Re-throwing ConversionException: " + cex.getMessage());
277 log().debug(" " + DEFAULT_CONFIG_MSG);
278 }
279 } else {
280 String msg = "Error converting from '" + toString(value.getClass()) +
281 "' to '" + toString(type) + "' " + cause.getMessage();
282 cex = new ConversionException(msg, cause);
283 if (log().isDebugEnabled()) {
284 log().debug(" Throwing ConversionException: " + msg);
285 log().debug(" " + DEFAULT_CONFIG_MSG);
286 }
287 BeanUtils.initCause(cex, cause);
288 }
289
290 throw cex;
291
292 }
293
294 /***
295 * Handle missing values.
296 * <p>
297 * If a default value has been specified then it is returned
298 * otherwise a ConversionException is thrown.
299 *
300 * @param type Data type to which this value should be converted.
301 * @return The default value.
302 * @throws ConversionException if no default value has been
303 * specified for this {@link Converter}.
304 */
305 protected Object handleMissing(Class type) {
306
307 if (useDefault || type.equals(String.class)) {
308 Object value = getDefault(type);
309 if (useDefault && value != null && !(type.equals(value.getClass()))) {
310 try {
311 value = convertToType(type, defaultValue);
312 } catch (Throwable t) {
313 log().error(" Default conversion to " + toString(type)
314 + "failed: " + t);
315 }
316 }
317 if (log().isDebugEnabled()) {
318 log().debug(" Using default "
319 + (value == null ? "" : toString(value.getClass()) + " ")
320 + "value '" + defaultValue + "'");
321 }
322 return value;
323 }
324
325 ConversionException cex = new ConversionException("No value specified for '" +
326 toString(type) + "'");
327 if (log().isDebugEnabled()) {
328 log().debug(" Throwing ConversionException: " + cex.getMessage());
329 log().debug(" " + DEFAULT_CONFIG_MSG);
330 }
331 throw cex;
332
333 }
334
335 /***
336 * Set the default value, converting as required.
337 * <p>
338 * If the default value is different from the type the
339 * <code>Converter</code> handles, it will be converted
340 * to the handled type.
341 *
342 * @param defaultValue The default value to be returned
343 * if the value to be converted is missing or an error
344 * occurs converting the value.
345 * @throws ConversionException if an error occurs converting
346 * the default value
347 */
348 protected void setDefaultValue(Object defaultValue) {
349 useDefault = false;
350 if (log().isDebugEnabled()) {
351 log().debug("Setting default value: " + defaultValue);
352 }
353 if (defaultValue == null) {
354 this.defaultValue = null;
355 } else {
356 this.defaultValue = convert(getDefaultType(), defaultValue);
357 }
358 useDefault = true;
359 }
360
361 /***
362 * Return the default type this <code>Converter</code> handles.
363 *
364 * @return The default type this <code>Converter</code> handles.
365 */
366 protected Class getDefaultType() {
367 return defaultType;
368 }
369
370 /***
371 * Return the default value for conversions to the specified
372 * type.
373 * @param type Data type to which this value should be converted.
374 * @return The default value for the specified type.
375 */
376 protected Object getDefault(Class type) {
377 if (type.equals(String.class)) {
378 return null;
379 } else {
380 return defaultValue;
381 }
382 }
383
384 /***
385 * Provide a String representation of this converter.
386 *
387 * @return A String representation of this converter
388 */
389 public String toString() {
390 return toString(getClass()) + "[UseDefault=" + useDefault + "]";
391 }
392
393
394
395 /***
396 * Accessor method for Log instance.
397 * <p>
398 * The Log instance variable is transient and
399 * accessing it through this method ensures it
400 * is re-initialized when this instance is
401 * de-serialized.
402 *
403 * @return The Log instance.
404 */
405 Log log() {
406 if (log == null) {
407 log = LogFactory.getLog(getClass());
408 }
409 return log;
410 }
411
412 /***
413 * Change primitve Class types to the associated wrapper class.
414 * @param type The class type to check.
415 * @return The converted type.
416 */
417 Class primitive(Class type) {
418 if (type == null || !type.isPrimitive()) {
419 return type;
420 }
421
422 if (type == Integer.TYPE) {
423 return Integer.class;
424 } else if (type == Double.TYPE) {
425 return Double.class;
426 } else if (type == Long.TYPE) {
427 return Long.class;
428 } else if (type == Boolean.TYPE) {
429 return Boolean.class;
430 } else if (type == Float.TYPE) {
431 return Float.class;
432 } else if (type == Short.TYPE) {
433 return Short.class;
434 } else if (type == Byte.TYPE) {
435 return Byte.class;
436 } else if (type == Character.TYPE) {
437 return Character.class;
438 } else {
439 return type;
440 }
441 }
442
443 /***
444 * Provide a String representation of a <code>java.lang.Class</code>.
445 * @param type The <code>java.lang.Class</code>.
446 * @return The String representation.
447 */
448 String toString(Class type) {
449 String typeName = null;
450 if (type == null) {
451 typeName = "null";
452 } else if (type.isArray()) {
453 Class elementType = type.getComponentType();
454 int count = 1;
455 while (elementType.isArray()) {
456 elementType = elementType .getComponentType();
457 count++;
458 }
459 typeName = elementType.getName();
460 for (int i = 0; i < count; i++) {
461 typeName += "[]";
462 }
463 } else {
464 typeName = type.getName();
465 }
466 if (typeName.startsWith("java.lang.") ||
467 typeName.startsWith("java.util.") ||
468 typeName.startsWith("java.math.")) {
469 typeName = typeName.substring("java.lang.".length());
470 } else if (typeName.startsWith(PACKAGE)) {
471 typeName = typeName.substring(PACKAGE.length());
472 }
473 return typeName;
474 }
475 }