001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.beanutils.converters;
018:
019: import java.lang.reflect.Array;
020: import java.util.Collection;
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.apache.commons.beanutils.BeanUtils;
024: import org.apache.commons.beanutils.ConversionException;
025: import org.apache.commons.beanutils.Converter;
026:
027: /**
028: * Base {@link Converter} implementation that provides the structure
029: * for handling conversion <b>to</b> and <b>from</b> a specified type.
030: * <p>
031: * This implementation provides the basic structure for
032: * converting to/from a specified type optionally using a default
033: * value or throwing a {@link ConversionException} if a
034: * conversion error occurs.
035: * <p>
036: * Implementations should provide conversion to the specified
037: * type and from the specified type to a <code>String</code> value
038: * by implementing the following methods:
039: * <ul>
040: * <li><code>convertToString(value)</code> - convert to a String
041: * (default implementation uses the objects <code>toString()</code>
042: * method).</li>
043: * <li><code>convertToType(Class, value)</code> - convert
044: * to the specified type</li>
045: * </ul>
046: *
047: * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
048: * @since 1.8.0
049: */
050: public abstract class AbstractConverter implements Converter {
051:
052: /** Debug logging message to indicate default value configuration */
053: private static final String DEFAULT_CONFIG_MSG = "(N.B. Converters can be configured to use default values to avoid throwing exceptions)";
054:
055: /** Current package name */
056: // getPackage() below returns null on some platforms/jvm versions during the unit tests.
057: // private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + ".";
058: private static final String PACKAGE = "org.apache.commons.beanutils.converters.";
059:
060: /**
061: * Logging for this instance.
062: */
063: private transient Log log;
064:
065: /**
066: * The default type this <code>Converter</code> handles.
067: */
068: private Class defaultType = null;
069:
070: /**
071: * Should we return the default value on conversion errors?
072: */
073: private boolean useDefault = false;
074:
075: /**
076: * The default value specified to our Constructor, if any.
077: */
078: private Object defaultValue = null;
079:
080: // ----------------------------------------------------------- Constructors
081:
082: /**
083: * Construct a <i>Converter</i> that throws a
084: * <code>ConversionException</code> if an error occurs.
085: *
086: * @param defaultType The default type this <code>Converter</code>
087: * handles
088: */
089: public AbstractConverter(Class defaultType) {
090: this .defaultType = defaultType;
091: if (defaultType == null) {
092: throw new IllegalArgumentException(
093: "Default type is missing.");
094: }
095: }
096:
097: /**
098: * Construct a <i>Converter</i> that returns a default
099: * 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: // --------------------------------------------------------- Public Methods
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()
140: : type);
141:
142: if (log().isDebugEnabled()) {
143: log().debug(
144: "Converting"
145: + (value == null ? "" : " '"
146: + toString(sourceType) + "'")
147: + " value '" + value + "' to type '"
148: + toString(targetType) + "'");
149: }
150:
151: value = convertArray(value);
152:
153: // Missing Value
154: if (value == null) {
155: return handleMissing(targetType);
156: }
157:
158: sourceType = value.getClass();
159:
160: try {
161: // Convert --> String
162: if (targetType.equals(String.class)) {
163: return convertToString(value);
164:
165: // No conversion necessary
166: } else if (targetType.equals(sourceType)) {
167: if (log().isDebugEnabled()) {
168: log().debug(
169: " No conversion required, value is already a "
170: + toString(targetType));
171: }
172: return value;
173:
174: // Convert --> Type
175: } else {
176: Object result = convertToType(targetType, value);
177: if (log().isDebugEnabled()) {
178: log().debug(
179: " Converted to " + toString(targetType)
180: + " value '" + result + "'");
181: }
182: return result;
183: }
184: } catch (Throwable t) {
185: return handleError(targetType, value, t);
186: }
187:
188: }
189:
190: /**
191: * Convert the input object into a String.
192: * <p>
193: * <b>N.B.</b>This implementation simply uses the value's
194: * <code>toString()</code> method and should be overriden if a
195: * more sophisticated mechanism for <i>conversion to a String</i>
196: * is required.
197: *
198: * @param value The input value to be converted.
199: * @return the converted String value.
200: * @throws Throwable if an error occurs converting to a String
201: */
202: protected String convertToString(Object value) throws Throwable {
203: return value.toString();
204: }
205:
206: /**
207: * Convert the input object into an output object of the
208: * specified type.
209: * <p>
210: * Typical implementations will provide a minimum of
211: * <code>String --> type</code> conversion.
212: *
213: * @param type Data type to which this value should be converted.
214: * @param value The input value to be converted.
215: * @return The converted value.
216: * @throws Throwable if an error occurs converting to the specified type
217: */
218: protected abstract Object convertToType(Class type, Object value)
219: throws Throwable;
220:
221: /**
222: * Return the first element from an Array (or Collection)
223: * or the value unchanged if not an Array (or Collection).
224: *
225: * N.B. This needs to be overriden for array/Collection converters.
226: *
227: * @param value The value to convert
228: * @return The first element in an Array (or Collection)
229: * or the value unchanged if not an Array (or Collection)
230: */
231: protected Object convertArray(Object value) {
232: if (value == null) {
233: return null;
234: }
235: if (value.getClass().isArray()) {
236: if (Array.getLength(value) > 0) {
237: return Array.get(value, 0);
238: } else {
239: return null;
240: }
241: }
242: if (value instanceof Collection) {
243: Collection collection = (Collection) value;
244: if (collection.size() > 0) {
245: return collection.iterator().next();
246: } else {
247: return null;
248: }
249: }
250: return value;
251: }
252:
253: /**
254: * Handle Conversion Errors.
255: * <p>
256: * If a default value has been specified then it is returned
257: * otherwise a ConversionException is thrown.
258: *
259: * @param type Data type to which this value should be converted.
260: * @param value The input value to be converted
261: * @param cause The exception thrown by the <code>convert</code> method
262: * @return The default value.
263: * @throws ConversionException if no default value has been
264: * specified for this {@link Converter}.
265: */
266: protected Object handleError(Class type, Object value,
267: Throwable cause) {
268: if (log().isDebugEnabled()) {
269: if (cause instanceof ConversionException) {
270: log().debug(
271: " Conversion threw ConversionException: "
272: + cause.getMessage());
273: } else {
274: log().debug(" Conversion threw " + cause);
275: }
276: }
277:
278: if (useDefault) {
279: return handleMissing(type);
280: }
281:
282: ConversionException cex = null;
283: if (cause instanceof ConversionException) {
284: cex = (ConversionException) cause;
285: if (log().isDebugEnabled()) {
286: log().debug(
287: " Re-throwing ConversionException: "
288: + cex.getMessage());
289: log().debug(" " + DEFAULT_CONFIG_MSG);
290: }
291: } else {
292: String msg = "Error converting from '"
293: + toString(value.getClass()) + "' to '"
294: + toString(type) + "' " + cause.getMessage();
295: cex = new ConversionException(msg, cause);
296: if (log().isDebugEnabled()) {
297: log().debug(" Throwing ConversionException: " + msg);
298: log().debug(" " + DEFAULT_CONFIG_MSG);
299: }
300: BeanUtils.initCause(cex, cause);
301: }
302:
303: throw cex;
304:
305: }
306:
307: /**
308: * Handle missing values.
309: * <p>
310: * If a default value has been specified then it is returned
311: * otherwise a ConversionException is thrown.
312: *
313: * @param type Data type to which this value should be converted.
314: * @return The default value.
315: * @throws ConversionException if no default value has been
316: * specified for this {@link Converter}.
317: */
318: protected Object handleMissing(Class type) {
319:
320: if (useDefault || type.equals(String.class)) {
321: Object value = getDefault(type);
322: if (useDefault && value != null
323: && !(type.equals(value.getClass()))) {
324: try {
325: value = convertToType(type, defaultValue);
326: } catch (Throwable t) {
327: log().error(
328: " Default conversion to "
329: + toString(type) + "failed: " + t);
330: }
331: }
332: if (log().isDebugEnabled()) {
333: log().debug(
334: " Using default "
335: + (value == null ? "" : toString(value
336: .getClass())
337: + " ") + "value '"
338: + defaultValue + "'");
339: }
340: return value;
341: }
342:
343: ConversionException cex = new ConversionException(
344: "No value specified for '" + toString(type) + "'");
345: if (log().isDebugEnabled()) {
346: log().debug(
347: " Throwing ConversionException: "
348: + cex.getMessage());
349: log().debug(" " + DEFAULT_CONFIG_MSG);
350: }
351: throw cex;
352:
353: }
354:
355: /**
356: * Set the default value, converting as required.
357: * <p>
358: * If the default value is different from the type the
359: * <code>Converter</code> handles, it will be converted
360: * to the handled type.
361: *
362: * @param defaultValue The default value to be returned
363: * if the value to be converted is missing or an error
364: * occurs converting the value.
365: * @throws ConversionException if an error occurs converting
366: * the default value
367: */
368: protected void setDefaultValue(Object defaultValue) {
369: useDefault = false;
370: if (log().isDebugEnabled()) {
371: log().debug("Setting default value: " + defaultValue);
372: }
373: if (defaultValue == null) {
374: this .defaultValue = null;
375: } else {
376: this .defaultValue = convert(getDefaultType(), defaultValue);
377: }
378: useDefault = true;
379: }
380:
381: /**
382: * Return the default type this <code>Converter</code> handles.
383: *
384: * @return The default type this <code>Converter</code> handles.
385: */
386: protected Class getDefaultType() {
387: return defaultType;
388: }
389:
390: /**
391: * Return the default value for conversions to the specified
392: * type.
393: * @param type Data type to which this value should be converted.
394: * @return The default value for the specified type.
395: */
396: protected Object getDefault(Class type) {
397: if (type.equals(String.class)) {
398: return null;
399: } else {
400: return defaultValue;
401: }
402: }
403:
404: /**
405: * Provide a String representation of this converter.
406: *
407: * @return A String representation of this converter
408: */
409: public String toString() {
410: return toString(getClass()) + "[UseDefault=" + useDefault + "]";
411: }
412:
413: // ----------------------------------------------------------- Package Methods
414:
415: /**
416: * Accessor method for Log instance.
417: * <p>
418: * The Log instance variable is transient and
419: * accessing it through this method ensures it
420: * is re-initialized when this instance is
421: * de-serialized.
422: *
423: * @return The Log instance.
424: */
425: Log log() {
426: if (log == null) {
427: log = LogFactory.getLog(getClass());
428: }
429: return log;
430: }
431:
432: /**
433: * Change primitve Class types to the associated wrapper class.
434: * @param type The class type to check.
435: * @return The converted type.
436: */
437: Class primitive(Class type) {
438: if (type == null || !type.isPrimitive()) {
439: return type;
440: }
441:
442: if (type == Integer.TYPE) {
443: return Integer.class;
444: } else if (type == Double.TYPE) {
445: return Double.class;
446: } else if (type == Long.TYPE) {
447: return Long.class;
448: } else if (type == Boolean.TYPE) {
449: return Boolean.class;
450: } else if (type == Float.TYPE) {
451: return Float.class;
452: } else if (type == Short.TYPE) {
453: return Short.class;
454: } else if (type == Byte.TYPE) {
455: return Byte.class;
456: } else if (type == Character.TYPE) {
457: return Character.class;
458: } else {
459: return type;
460: }
461: }
462:
463: /**
464: * Provide a String representation of a <code>java.lang.Class</code>.
465: * @param type The <code>java.lang.Class</code>.
466: * @return The String representation.
467: */
468: String toString(Class type) {
469: String typeName = null;
470: if (type == null) {
471: typeName = "null";
472: } else if (type.isArray()) {
473: Class elementType = type.getComponentType();
474: int count = 1;
475: while (elementType.isArray()) {
476: elementType = elementType.getComponentType();
477: count++;
478: }
479: typeName = elementType.getName();
480: for (int i = 0; i < count; i++) {
481: typeName += "[]";
482: }
483: } else {
484: typeName = type.getName();
485: }
486: if (typeName.startsWith("java.lang.")
487: || typeName.startsWith("java.util.")
488: || typeName.startsWith("java.math.")) {
489: typeName = typeName.substring("java.lang.".length());
490: } else if (typeName.startsWith(PACKAGE)) {
491: typeName = typeName.substring(PACKAGE.length());
492: }
493: return typeName;
494: }
495: }
|