Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractConverter |
|
| 5.6875;5.688 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
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: 640131 $ $Date: 2008-03-22 22:10:31 -0400 (Sat, 22 Mar 2008) $ | |
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 | /** Current package name */ | |
57 | // getPackage() below returns null on some platforms/jvm versions during the unit tests. | |
58 | // private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + "."; | |
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 | * Should we return the default value on conversion errors? | |
68 | */ | |
69 | 30225 | private boolean useDefault = false; |
70 | ||
71 | /** | |
72 | * The default value specified to our Constructor, if any. | |
73 | */ | |
74 | 30225 | private Object defaultValue = null; |
75 | ||
76 | // ----------------------------------------------------------- Constructors | |
77 | ||
78 | /** | |
79 | * Construct a <i>Converter</i> that throws a | |
80 | * <code>ConversionException</code> if an error occurs. | |
81 | */ | |
82 | 29034 | public AbstractConverter() { |
83 | 29034 | } |
84 | ||
85 | /** | |
86 | * Construct a <i>Converter</i> that returns a default | |
87 | * value if an error occurs. | |
88 | * | |
89 | * @param defaultValue The default value to be returned | |
90 | * if the value to be converted is missing or an error | |
91 | * occurs converting the value. | |
92 | */ | |
93 | 1191 | public AbstractConverter(Object defaultValue) { |
94 | 1191 | setDefaultValue(defaultValue); |
95 | 1191 | } |
96 | ||
97 | // --------------------------------------------------------- Public Methods | |
98 | ||
99 | /** | |
100 | * Indicates whether a default value will be returned or exception | |
101 | * thrown in the event of a conversion error. | |
102 | * | |
103 | * @return <code>true</code> if a default value will be returned for | |
104 | * conversion errors or <code>false</code> if a {@link ConversionException} | |
105 | * will be thrown. | |
106 | */ | |
107 | public boolean isUseDefault() { | |
108 | 182 | return useDefault; |
109 | } | |
110 | ||
111 | /** | |
112 | * Convert the input object into an output object of the | |
113 | * specified type. | |
114 | * | |
115 | * @param type Data type to which this value should be converted | |
116 | * @param value The input value to be converted | |
117 | * @return The converted value. | |
118 | * @throws ConversionException if conversion cannot be performed | |
119 | * successfully and no default is specified. | |
120 | */ | |
121 | public Object convert(Class type, Object value) { | |
122 | ||
123 | 18498 | Class sourceType = value == null ? null : value.getClass(); |
124 | 18498 | Class targetType = primitive(type == null ? getDefaultType() : type); |
125 | ||
126 | 18498 | if (log().isDebugEnabled()) { |
127 | 0 | log().debug("Converting" |
128 | + (value == null ? "" : " '" + toString(sourceType) + "'") | |
129 | + " value '" + value + "' to type '" + toString(targetType) + "'"); | |
130 | } | |
131 | ||
132 | 18498 | value = convertArray(value); |
133 | ||
134 | // Missing Value | |
135 | 18498 | if (value == null) { |
136 | 112 | return handleMissing(targetType); |
137 | } | |
138 | ||
139 | 18386 | sourceType = value.getClass(); |
140 | ||
141 | try { | |
142 | // Convert --> String | |
143 | 18386 | if (targetType.equals(String.class)) { |
144 | 759 | return convertToString(value); |
145 | ||
146 | // No conversion necessary | |
147 | 17627 | } else if (targetType.equals(sourceType)) { |
148 | 12740 | if (log().isDebugEnabled()) { |
149 | 0 | log().debug(" No conversion required, value is already a " |
150 | + toString(targetType)); | |
151 | } | |
152 | 12740 | return value; |
153 | ||
154 | // Convert --> Type | |
155 | } else { | |
156 | 4887 | Object result = convertToType(targetType, value); |
157 | 4634 | if (log().isDebugEnabled()) { |
158 | 0 | log().debug(" Converted to " + toString(targetType) + |
159 | " value '" + result + "'"); | |
160 | } | |
161 | 4634 | return result; |
162 | } | |
163 | 253 | } catch (Throwable t) { |
164 | 253 | return handleError(targetType, value, t); |
165 | } | |
166 | ||
167 | } | |
168 | ||
169 | /** | |
170 | * Convert the input object into a String. | |
171 | * <p> | |
172 | * <b>N.B.</b>This implementation simply uses the value's | |
173 | * <code>toString()</code> method and should be overriden if a | |
174 | * more sophisticated mechanism for <i>conversion to a String</i> | |
175 | * is required. | |
176 | * | |
177 | * @param value The input value to be converted. | |
178 | * @return the converted String value. | |
179 | * @throws Throwable if an error occurs converting to a String | |
180 | */ | |
181 | protected String convertToString(Object value) throws Throwable { | |
182 | 573 | return value.toString(); |
183 | } | |
184 | ||
185 | /** | |
186 | * Convert the input object into an output object of the | |
187 | * specified type. | |
188 | * <p> | |
189 | * Typical implementations will provide a minimum of | |
190 | * <code>String --> type</code> conversion. | |
191 | * | |
192 | * @param type Data type to which this value should be converted. | |
193 | * @param value The input value to be converted. | |
194 | * @return The converted value. | |
195 | * @throws Throwable if an error occurs converting to the specified type | |
196 | */ | |
197 | protected abstract Object convertToType(Class type, Object value) throws Throwable; | |
198 | ||
199 | /** | |
200 | * Return the first element from an Array (or Collection) | |
201 | * or the value unchanged if not an Array (or Collection). | |
202 | * | |
203 | * N.B. This needs to be overriden for array/Collection converters. | |
204 | * | |
205 | * @param value The value to convert | |
206 | * @return The first element in an Array (or Collection) | |
207 | * or the value unchanged if not an Array (or Collection) | |
208 | */ | |
209 | protected Object convertArray(Object value) { | |
210 | 8661 | if (value == null) { |
211 | 94 | return null; |
212 | } | |
213 | 8567 | if (value.getClass().isArray()) { |
214 | 40 | if (Array.getLength(value) > 0) { |
215 | 40 | return Array.get(value, 0); |
216 | } else { | |
217 | 0 | return null; |
218 | } | |
219 | } | |
220 | 8527 | if (value instanceof Collection) { |
221 | 2 | Collection collection = (Collection)value; |
222 | 2 | if (collection.size() > 0) { |
223 | 2 | return collection.iterator().next(); |
224 | } else { | |
225 | 0 | return null; |
226 | } | |
227 | } | |
228 | 8525 | return value; |
229 | } | |
230 | ||
231 | /** | |
232 | * Handle Conversion Errors. | |
233 | * <p> | |
234 | * If a default value has been specified then it is returned | |
235 | * otherwise a ConversionException is thrown. | |
236 | * | |
237 | * @param type Data type to which this value should be converted. | |
238 | * @param value The input value to be converted | |
239 | * @param cause The exception thrown by the <code>convert</code> method | |
240 | * @return The default value. | |
241 | * @throws ConversionException if no default value has been | |
242 | * specified for this {@link Converter}. | |
243 | */ | |
244 | protected Object handleError(Class type, Object value, Throwable cause) { | |
245 | 253 | if (log().isDebugEnabled()) { |
246 | 0 | if (cause instanceof ConversionException) { |
247 | 0 | log().debug(" Conversion threw ConversionException: " + cause.getMessage()); |
248 | } else { | |
249 | 0 | log().debug(" Conversion threw " + cause); |
250 | } | |
251 | } | |
252 | ||
253 | 253 | if (useDefault) { |
254 | 81 | return handleMissing(type); |
255 | } | |
256 | ||
257 | 172 | ConversionException cex = null; |
258 | 172 | if (cause instanceof ConversionException) { |
259 | 131 | cex = (ConversionException)cause; |
260 | 131 | if (log().isDebugEnabled()) { |
261 | 0 | log().debug(" Re-throwing ConversionException: " + cex.getMessage()); |
262 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
263 | } | |
264 | } else { | |
265 | 41 | String msg = "Error converting from '" + toString(value.getClass()) + |
266 | "' to '" + toString(type) + "' " + cause.getMessage(); | |
267 | 41 | cex = new ConversionException(msg, cause); |
268 | 41 | if (log().isDebugEnabled()) { |
269 | 0 | log().debug(" Throwing ConversionException: " + msg); |
270 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
271 | } | |
272 | 41 | BeanUtils.initCause(cex, cause); |
273 | } | |
274 | ||
275 | 172 | throw cex; |
276 | ||
277 | } | |
278 | ||
279 | /** | |
280 | * Handle missing values. | |
281 | * <p> | |
282 | * If a default value has been specified then it is returned | |
283 | * otherwise a ConversionException is thrown. | |
284 | * | |
285 | * @param type Data type to which this value should be converted. | |
286 | * @return The default value. | |
287 | * @throws ConversionException if no default value has been | |
288 | * specified for this {@link Converter}. | |
289 | */ | |
290 | protected Object handleMissing(Class type) { | |
291 | ||
292 | 215 | if (useDefault || type.equals(String.class)) { |
293 | 170 | Object value = getDefault(type); |
294 | 170 | if (useDefault && value != null && !(type.equals(value.getClass()))) { |
295 | try { | |
296 | 20 | value = convertToType(type, defaultValue); |
297 | 0 | } catch (Throwable t) { |
298 | 0 | log().error(" Default conversion to " + toString(type) |
299 | + "failed: " + t); | |
300 | 20 | } |
301 | } | |
302 | 170 | if (log().isDebugEnabled()) { |
303 | 0 | log().debug(" Using default " |
304 | + (value == null ? "" : toString(value.getClass()) + " ") | |
305 | + "value '" + defaultValue + "'"); | |
306 | } | |
307 | 170 | return value; |
308 | } | |
309 | ||
310 | 45 | ConversionException cex = new ConversionException("No value specified for '" + |
311 | toString(type) + "'"); | |
312 | 45 | if (log().isDebugEnabled()) { |
313 | 0 | log().debug(" Throwing ConversionException: " + cex.getMessage()); |
314 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
315 | } | |
316 | 45 | throw cex; |
317 | ||
318 | } | |
319 | ||
320 | /** | |
321 | * Set the default value, converting as required. | |
322 | * <p> | |
323 | * If the default value is different from the type the | |
324 | * <code>Converter</code> handles, it will be converted | |
325 | * to the handled type. | |
326 | * | |
327 | * @param defaultValue The default value to be returned | |
328 | * if the value to be converted is missing or an error | |
329 | * occurs converting the value. | |
330 | * @throws ConversionException if an error occurs converting | |
331 | * the default value | |
332 | */ | |
333 | protected void setDefaultValue(Object defaultValue) { | |
334 | 16633 | useDefault = false; |
335 | 16633 | if (log().isDebugEnabled()) { |
336 | 0 | log().debug("Setting default value: " + defaultValue); |
337 | } | |
338 | 16633 | if (defaultValue == null) { |
339 | 118 | this.defaultValue = null; |
340 | } else { | |
341 | 16515 | this.defaultValue = convert(getDefaultType(), defaultValue); |
342 | } | |
343 | 16633 | useDefault = true; |
344 | 16633 | } |
345 | ||
346 | /** | |
347 | * Return the default type this <code>Converter</code> handles. | |
348 | * | |
349 | * @return The default type this <code>Converter</code> handles. | |
350 | */ | |
351 | protected abstract Class getDefaultType(); | |
352 | ||
353 | /** | |
354 | * Return the default value for conversions to the specified | |
355 | * type. | |
356 | * @param type Data type to which this value should be converted. | |
357 | * @return The default value for the specified type. | |
358 | */ | |
359 | protected Object getDefault(Class type) { | |
360 | 169 | if (type.equals(String.class)) { |
361 | 36 | return null; |
362 | } else { | |
363 | 133 | return defaultValue; |
364 | } | |
365 | } | |
366 | ||
367 | /** | |
368 | * Provide a String representation of this converter. | |
369 | * | |
370 | * @return A String representation of this converter | |
371 | */ | |
372 | public String toString() { | |
373 | 50 | return toString(getClass()) + "[UseDefault=" + useDefault + "]"; |
374 | } | |
375 | ||
376 | // ----------------------------------------------------------- Package Methods | |
377 | ||
378 | /** | |
379 | * Accessor method for Log instance. | |
380 | * <p> | |
381 | * The Log instance variable is transient and | |
382 | * accessing it through this method ensures it | |
383 | * is re-initialized when this instance is | |
384 | * de-serialized. | |
385 | * | |
386 | * @return The Log instance. | |
387 | */ | |
388 | Log log() { | |
389 | 54347 | if (log == null) { |
390 | 16935 | log = LogFactory.getLog(getClass()); |
391 | } | |
392 | 54347 | return log; |
393 | } | |
394 | ||
395 | /** | |
396 | * Change primitve Class types to the associated wrapper class. | |
397 | * @param type The class type to check. | |
398 | * @return The converted type. | |
399 | */ | |
400 | Class primitive(Class type) { | |
401 | 18498 | if (type == null || !type.isPrimitive()) { |
402 | 18004 | return type; |
403 | } | |
404 | ||
405 | 494 | if (type == Integer.TYPE) { |
406 | 159 | return Integer.class; |
407 | 335 | } else if (type == Double.TYPE) { |
408 | 60 | return Double.class; |
409 | 275 | } else if (type == Long.TYPE) { |
410 | 73 | return Long.class; |
411 | 202 | } else if (type == Boolean.TYPE) { |
412 | 42 | return Boolean.class; |
413 | 160 | } else if (type == Float.TYPE) { |
414 | 54 | return Float.class; |
415 | 106 | } else if (type == Short.TYPE) { |
416 | 54 | return Short.class; |
417 | 52 | } else if (type == Byte.TYPE) { |
418 | 51 | return Byte.class; |
419 | 1 | } else if (type == Character.TYPE) { |
420 | 1 | return Character.class; |
421 | } else { | |
422 | 0 | return type; |
423 | } | |
424 | } | |
425 | ||
426 | /** | |
427 | * Provide a String representation of a <code>java.lang.Class</code>. | |
428 | * @param type The <code>java.lang.Class</code>. | |
429 | * @return The String representation. | |
430 | */ | |
431 | String toString(Class type) { | |
432 | 709 | String typeName = null; |
433 | 709 | if (type == null) { |
434 | 0 | typeName = "null"; |
435 | 709 | } else if (type.isArray()) { |
436 | 0 | Class elementType = type.getComponentType(); |
437 | 0 | int count = 1; |
438 | 0 | while (elementType.isArray()) { |
439 | 0 | elementType = elementType .getComponentType(); |
440 | 0 | count++; |
441 | } | |
442 | 0 | typeName = elementType.getName(); |
443 | 0 | for (int i = 0; i < count; i++) { |
444 | 0 | typeName += "[]"; |
445 | } | |
446 | 0 | } else { |
447 | 709 | typeName = type.getName(); |
448 | } | |
449 | 709 | if (typeName.startsWith("java.lang.") || |
450 | typeName.startsWith("java.util.") || | |
451 | typeName.startsWith("java.math.")) { | |
452 | 366 | typeName = typeName.substring("java.lang.".length()); |
453 | 343 | } else if (typeName.startsWith(PACKAGE)) { |
454 | 255 | typeName = typeName.substring(PACKAGE.length()); |
455 | } | |
456 | 709 | return typeName; |
457 | } | |
458 | } |