001: /*
002: * Copyright (c) 2002-2007 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork.util;
006:
007: import java.lang.reflect.Array;
008: import java.lang.reflect.Constructor;
009: import java.lang.reflect.Member;
010: import java.math.BigDecimal;
011: import java.math.BigInteger;
012: import java.text.DateFormat;
013: import java.text.NumberFormat;
014: import java.text.ParseException;
015: import java.text.ParsePosition;
016: import java.text.SimpleDateFormat;
017: import java.util.ArrayList;
018: import java.util.Collection;
019: import java.util.Date;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Locale;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.SortedSet;
027: import java.util.TreeSet;
028:
029: import ognl.DefaultTypeConverter;
030: import ognl.Ognl;
031: import ognl.TypeConverter;
032:
033: import com.opensymphony.xwork.ActionContext;
034: import com.opensymphony.xwork.XworkException;
035: import com.opensymphony.util.TextUtils;
036:
037: /**
038: * <!-- START SNIPPET: javadoc -->
039: * XWork will automatically handle the most common type conversion for you.
040: * <p/>
041: * This includes support for converting to and from Strings for each of the following:
042: * <ul>
043: * <li>String</li>
044: * <li>boolean / Boolean</li>
045: * <li>char / Character</li>
046: * <li>int / Integer, float / Float, long / Long, double / Double</li>
047: * <li>dates - uses the SHORT or RFC3339 format (<code>yyyy-MM-dd'T'HH:mm:ss</code>) for the Locale associated with the current request</li>
048: * <li>arrays - assuming the individual strings can be coverted to the individual items</li>
049: * <li>collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is
050: * created</li>
051: * </ul>
052: * <p/>
053: * <b>Note:</b> that with arrays the type conversion will defer to the type of the array elements and try to convert each
054: * item individually. As with any other type conversion, if the conversion can't be performed the standard type
055: * conversion error reporting is used to indicate a problem occured while processing the type conversion.
056: * <!-- END SNIPPET: javadoc -->
057: *
058: * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
059: * @author Mike Mosiewicz
060: * @author Rainer Hermanns
061: * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
062: */
063: public class XWorkBasicConverter extends DefaultTypeConverter {
064:
065: private static final String MILLISECOND_FORMAT = ".SSS";
066: private static final String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
067:
068: public Object convertValue(Map context, Object o, Member member,
069: String s, Object value, Class toType) {
070: Object result = null;
071:
072: if (value == null || toType.isAssignableFrom(value.getClass())) {
073: // no need to convert at all, right?
074: return value;
075: }
076:
077: if (toType == String.class) {
078: result = doConvertToString(context, value);
079: } else if (toType == boolean.class) {
080: result = doConvertToBoolean(value);
081: } else if (toType == Boolean.class) {
082: result = doConvertToBoolean(value);
083: } else if (toType.isArray()) {
084: result = doConvertToArray(context, o, member, s, value,
085: toType);
086: } else if (Date.class.isAssignableFrom(toType)) {
087: result = doConvertToDate(context, value, toType);
088: } else if (Collection.class.isAssignableFrom(toType)) {
089: result = doConvertToCollection(context, o, member, s,
090: value, toType);
091: } else if (toType == Character.class) {
092: result = doConvertToCharacter(value);
093: } else if (toType == char.class) {
094: result = doConvertToCharacter(value);
095: } else if (Number.class.isAssignableFrom(toType)) {
096: result = doConvertToNumber(context, value, toType);
097: } else if (toType == Class.class) {
098: result = doConvertToClass(value);
099: }
100:
101: if (result == null) {
102: if (value instanceof Object[]) {
103: Object[] array = (Object[]) value;
104:
105: if (array.length >= 1) {
106: value = array[0];
107: }
108:
109: // let's try to convert the first element only
110: result = convertValue(context, o, member, s, value,
111: toType);
112: } else if (!"".equals(value)) { // we've already tried the types we know
113: result = super .convertValue(context, value, toType);
114: }
115:
116: if (result == null && value != null && !"".equals(value)) {
117: throw new XworkException("Cannot create type " + toType
118: + " from value " + value);
119: }
120: }
121:
122: return result;
123: }
124:
125: private Locale getLocale(Map context) {
126: if (context == null) {
127: return Locale.getDefault();
128: }
129:
130: Locale locale = (Locale) context.get(ActionContext.LOCALE);
131:
132: if (locale == null) {
133: locale = Locale.getDefault();
134: }
135:
136: return locale;
137: }
138:
139: /**
140: * Creates a Collection of the specified type.
141: *
142: * @param fromObject
143: * @param propertyName
144: * @param toType the type of Collection to create
145: * @param memberType the type of object elements in this collection must be
146: * @param size the initial size of the collection (ignored if 0 or less)
147: * @return a Collection of the specified type
148: */
149: private Collection createCollection(Object fromObject,
150: String propertyName, Class toType, Class memberType,
151: int size) {
152: // try {
153: // Object original = Ognl.getValue(OgnlUtil.compile(propertyName),fromObject);
154: // if (original instanceof Collection) {
155: // Collection coll = (Collection) original;
156: // coll.clear();
157: // return coll;
158: // }
159: // } catch (Exception e) {
160: // // fail back to creating a new one
161: // }
162:
163: Collection result;
164:
165: if (toType == Set.class) {
166: if (size > 0) {
167: result = new HashSet(size);
168: } else {
169: result = new HashSet();
170: }
171: } else if (toType == SortedSet.class) {
172: result = new TreeSet();
173: } else {
174: if (size > 0) {
175: result = new XWorkList(memberType, size);
176: } else {
177: result = new XWorkList(memberType);
178: }
179: }
180:
181: return result;
182: }
183:
184: private Object doConvertToArray(Map context, Object o,
185: Member member, String s, Object value, Class toType) {
186: Object result = null;
187: Class componentType = toType.getComponentType();
188:
189: if (componentType != null) {
190: TypeConverter converter = Ognl.getTypeConverter(context);
191:
192: if (value.getClass().isArray()) {
193: int length = Array.getLength(value);
194: result = Array.newInstance(componentType, length);
195:
196: for (int i = 0; i < length; i++) {
197: Object valueItem = Array.get(value, i);
198: Array.set(result, i, converter.convertValue(
199: context, o, member, s, valueItem,
200: componentType));
201: }
202: } else {
203: result = Array.newInstance(componentType, 1);
204: Array.set(result, 0, converter.convertValue(context, o,
205: member, s, value, componentType));
206: }
207: }
208:
209: return result;
210: }
211:
212: private Object doConvertToCharacter(Object value) {
213: if (value instanceof String) {
214: String cStr = (String) value;
215:
216: return (cStr.length() > 0) ? new Character(cStr.charAt(0))
217: : null;
218: }
219:
220: return null;
221: }
222:
223: private Object doConvertToBoolean(Object value) {
224: if (value instanceof String) {
225: String bStr = (String) value;
226:
227: return Boolean.valueOf(bStr);
228: }
229:
230: return null;
231: }
232:
233: private Class doConvertToClass(Object value) {
234: Class clazz = null;
235:
236: if (value instanceof String && value != null
237: && ((String) value).length() > 0) {
238: try {
239: clazz = Class.forName((String) value);
240: } catch (ClassNotFoundException e) {
241: throw new XworkException(e.getLocalizedMessage(), e);
242: }
243: }
244:
245: return clazz;
246: }
247:
248: private Collection doConvertToCollection(Map context, Object o,
249: Member member, String prop, Object value, Class toType) {
250: Collection result;
251: Class memberType = String.class;
252:
253: if (o != null) {
254: //memberType = (Class) XWorkConverter.getInstance().getConverter(o.getClass(), XWorkConverter.CONVERSION_COLLECTION_PREFIX + prop);
255: memberType = XWorkConverter.getInstance()
256: .getObjectTypeDeterminer().getElementClass(
257: o.getClass(), prop, null);
258:
259: if (memberType == null) {
260: memberType = String.class;
261: }
262: }
263:
264: if (toType.isAssignableFrom(value.getClass())) {
265: // no need to do anything
266: result = (Collection) value;
267: } else if (value.getClass().isArray()) {
268: Object[] objArray = (Object[]) value;
269: TypeConverter converter = Ognl.getTypeConverter(context);
270: result = createCollection(o, prop, toType, memberType,
271: objArray.length);
272:
273: for (int i = 0; i < objArray.length; i++) {
274: result.add(converter.convertValue(context, o, member,
275: prop, objArray[i], memberType));
276: }
277: } else if (Collection.class.isAssignableFrom(value.getClass())) {
278: Collection col = (Collection) value;
279: TypeConverter converter = Ognl.getTypeConverter(context);
280: result = createCollection(o, prop, toType, memberType, col
281: .size());
282:
283: for (Iterator it = col.iterator(); it.hasNext();) {
284: result.add(converter.convertValue(context, o, member,
285: prop, it.next(), memberType));
286: }
287: } else {
288: result = createCollection(o, prop, toType, memberType, -1);
289: result.add(value);
290: }
291:
292: return result;
293: }
294:
295: private Object doConvertToDate(Map context, Object value,
296: Class toType) {
297: Date result = null;
298:
299: if (value instanceof String && value != null
300: && ((String) value).length() > 0) {
301: String sa = (String) value;
302: Locale locale = getLocale(context);
303:
304: DateFormat df = null;
305: if (java.sql.Time.class == toType) {
306: df = DateFormat.getTimeInstance(DateFormat.MEDIUM,
307: locale);
308: } else if (java.sql.Timestamp.class == toType) {
309: Date check = null;
310: SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat
311: .getDateTimeInstance(DateFormat.SHORT,
312: DateFormat.MEDIUM, locale);
313: SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt
314: .toPattern()
315: + MILLISECOND_FORMAT, locale);
316:
317: SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat
318: .getDateInstance(DateFormat.SHORT, locale);
319:
320: SimpleDateFormat rfc3339Format = new SimpleDateFormat(
321: RFC3339_FORMAT);
322:
323: SimpleDateFormat[] fmts = { fullfmt, dtfmt, dfmt,
324: rfc3339Format };
325: for (int i = 0; i < fmts.length; i++) {
326: try {
327: check = fmts[i].parse(sa);
328: df = fmts[i];
329: if (check != null) {
330: break;
331: }
332: } catch (ParseException ignore) {
333: }
334: }
335: } else if (java.util.Date.class == toType) {
336: Date check = null;
337: SimpleDateFormat d1 = (SimpleDateFormat) DateFormat
338: .getDateTimeInstance(DateFormat.SHORT,
339: DateFormat.LONG, locale);
340: SimpleDateFormat d2 = (SimpleDateFormat) DateFormat
341: .getDateTimeInstance(DateFormat.SHORT,
342: DateFormat.MEDIUM, locale);
343: SimpleDateFormat d3 = (SimpleDateFormat) DateFormat
344: .getDateTimeInstance(DateFormat.SHORT,
345: DateFormat.SHORT, locale);
346: SimpleDateFormat rfc3339Format = new SimpleDateFormat(
347: RFC3339_FORMAT);
348: SimpleDateFormat[] dfs = { d1, d2, d3, rfc3339Format }; //added RFC 3339 date format (XW-473)
349: for (int i = 0; i < dfs.length; i++) {
350: try {
351: check = dfs[i].parse(sa);
352: df = dfs[i];
353: if (check != null) {
354: break;
355: }
356: } catch (ParseException ignore) {
357: }
358: }
359: }
360: //final fallback for dates without time
361: if (df == null) {
362: df = DateFormat.getDateInstance(DateFormat.SHORT,
363: locale);
364: }
365: try {
366: df.setLenient(false); // let's use strict parsing (XW-341)
367: result = df.parse(sa);
368: if (!(Date.class == toType)) {
369: try {
370: Constructor constructor = toType
371: .getConstructor(new Class[] { long.class });
372: return constructor
373: .newInstance(new Object[] { new Long(
374: result.getTime()) });
375: } catch (Exception e) {
376: throw new XworkException(
377: "Couldn't create class "
378: + toType
379: + " using default (long) constructor",
380: e);
381: }
382: }
383: } catch (ParseException e) {
384: throw new XworkException("Could not parse date", e);
385: }
386: } else if (Date.class.isAssignableFrom(value.getClass())) {
387: result = (Date) value;
388: }
389: return result;
390: }
391:
392: private Object doConvertToNumber(Map context, Object value,
393: Class toType) {
394: if (value instanceof String) {
395: if (toType == BigDecimal.class) {
396: return new BigDecimal((String) value);
397: } else if (toType == BigInteger.class) {
398: return new BigInteger((String) value);
399: } else {
400: String stringValue = (String) value;
401: if (!toType.isPrimitive()
402: && (stringValue == null || stringValue.length() == 0)) {
403: return null;
404: }
405: NumberFormat numFormat = NumberFormat
406: .getInstance(getLocale(context));
407: ParsePosition parsePos = new ParsePosition(0);
408: if (isIntegerType(toType)) {
409: numFormat.setParseIntegerOnly(true);
410: }
411: numFormat.setGroupingUsed(true);
412: Number number = numFormat.parse(stringValue, parsePos);
413:
414: if (parsePos.getIndex() != stringValue.length()) {
415: throw new XworkException("Unparseable number: \""
416: + stringValue + "\" at position "
417: + parsePos.getIndex());
418: } else {
419: value = super .convertValue(context, number, toType);
420: }
421: }
422: } else if (value instanceof Object[]) {
423: Object[] objArray = (Object[]) value;
424:
425: if (objArray.length == 1) {
426: return doConvertToNumber(context, objArray[0], toType);
427: }
428: }
429:
430: // pass it through DefaultTypeConverter
431: return super .convertValue(context, value, toType);
432: }
433:
434: protected boolean isIntegerType(Class type) {
435: if (double.class == type || float.class == type
436: || Double.class == type || Float.class == type
437: || char.class == type || Character.class == type) {
438: return false;
439: }
440:
441: return true;
442: }
443:
444: private String doConvertToString(Map context, Object value) {
445: String result = null;
446:
447: if (value instanceof int[]) {
448: int[] x = (int[]) value;
449: List intArray = new ArrayList(x.length);
450:
451: for (int i = 0; i < x.length; i++) {
452: intArray.add(new Integer(x[i]));
453: }
454:
455: result = TextUtils.join(", ", intArray);
456: } else if (value instanceof long[]) {
457: long[] x = (long[]) value;
458: List intArray = new ArrayList(x.length);
459:
460: for (int i = 0; i < x.length; i++) {
461: intArray.add(new Long(x[i]));
462: }
463:
464: result = TextUtils.join(", ", intArray);
465: } else if (value instanceof double[]) {
466: double[] x = (double[]) value;
467: List intArray = new ArrayList(x.length);
468:
469: for (int i = 0; i < x.length; i++) {
470: intArray.add(new Double(x[i]));
471: }
472:
473: result = TextUtils.join(", ", intArray);
474: } else if (value instanceof boolean[]) {
475: boolean[] x = (boolean[]) value;
476: List intArray = new ArrayList(x.length);
477:
478: for (int i = 0; i < x.length; i++) {
479: intArray.add(new Boolean(x[i]));
480: }
481:
482: result = TextUtils.join(", ", intArray);
483: } else if (value instanceof Date) {
484: DateFormat df = null;
485: if (value instanceof java.sql.Time) {
486: df = DateFormat.getTimeInstance(DateFormat.MEDIUM,
487: getLocale(context));
488: } else if (value instanceof java.sql.Timestamp) {
489: SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat
490: .getDateTimeInstance(DateFormat.SHORT,
491: DateFormat.MEDIUM, getLocale(context));
492: df = new SimpleDateFormat(dfmt.toPattern()
493: + MILLISECOND_FORMAT);
494: } else {
495: df = DateFormat.getDateInstance(DateFormat.SHORT,
496: getLocale(context));
497: }
498: result = df.format(value);
499: } else if (value instanceof String[]) {
500: result = TextUtils.join(", ", (String[]) value);
501: }
502:
503: return result;
504: }
505: }
|