Fixing an issue with output parameters that are of type IntPtr
[castle.git] / Components / Binder / Castle.Components.Binder / DefaultConverter.cs
blob2e8b9a44fb250b685b545cda3452fd0078895724
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.Components.Binder
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Web;
23 public class DefaultConverter : MarshalByRefObject, IConverter
25 public bool CanConvert(Type desiredType, Type inputType, object input, out bool exactMatch)
27 exactMatch = false;
29 if (input == null)
31 return true;
33 else if (inputType == desiredType)
35 exactMatch = true;
37 else if (!desiredType.IsInstanceOfType(input))
39 TypeConverter conv = TypeDescriptor.GetConverter(desiredType);
40 return (conv != null && conv.CanConvertFrom(inputType));
43 return false;
46 public object Convert(Type desiredType, Type inputType, object input, out bool conversionSucceeded)
48 if (inputType == desiredType) // Nothing to convert
50 if (inputType == typeof(String))
52 if (input == null)
54 conversionSucceeded = false;
55 return null;
57 else if (input.ToString().Length == 0)
59 conversionSucceeded = true;
60 return null;
62 else
64 conversionSucceeded = true;
65 return input.ToString().Trim(' ');
68 else
70 conversionSucceeded = true;
71 return input;
75 return Convert(desiredType, input, out conversionSucceeded);
78 /// <summary>
79 /// Convert the input param into the desired type
80 /// </summary>
81 /// <param name="desiredType">Type of the desired</param>
82 /// <param name="input">The input</param>
83 /// <param name="conversionSucceeded">if <c>false</c> the return value must be ignored</param>
84 /// <remarks>
85 /// There are 3 possible cases when trying to convert:
86 /// 1) Input data for conversion missing (input is null or an empty String)
87 /// Returns default conversion value (based on desired type) and set <c>conversionSucceeded = false</c>
88 /// 2) Has input data but cannot convert to particular type
89 /// Throw exception and set <c>conversionSucceeded = false</c>
90 /// 3) Has input data and can convert to particular type
91 /// Return input converted to desired type and set <c>conversionSucceeded = true</c>
92 /// </remarks>
93 public object Convert(Type desiredType, object input, out bool conversionSucceeded)
95 try
97 conversionSucceeded = (input != null);
99 if (desiredType.IsInstanceOfType(input))
101 return input;
103 else if (desiredType == typeof(String))
105 if (conversionSucceeded && input.GetType() == typeof(string) && ((String) input) == String.Empty)
107 return null;
110 return conversionSucceeded ? input.ToString().Trim(' ') : null;
112 else if (desiredType.IsArray)
114 return conversionSucceeded
116 ConvertToArray(desiredType, input, ref conversionSucceeded)
117 : null;
119 else if (desiredType.IsEnum)
121 return ConvertEnum(desiredType, input, ref conversionSucceeded);
123 else if (desiredType == typeof(Decimal))
125 return ConvertDecimal(input, ref conversionSucceeded);
127 else if (desiredType.IsPrimitive)
129 return ConvertPrimitive(desiredType, input, ref conversionSucceeded);
131 else if (desiredType.IsGenericType &&
132 desiredType.GetGenericTypeDefinition() == typeof(Nullable<>))
134 return ConvertNullable(desiredType, input, ref conversionSucceeded);
136 else if (desiredType == typeof(Guid))
138 return ConvertGuid(input, ref conversionSucceeded);
140 else if (desiredType == typeof(DateTime))
142 return ConvertDate(input, ref conversionSucceeded);
144 else if (desiredType == typeof(HttpPostedFile))
146 return input;
148 else if (DataBinder.IsGenericList(desiredType))
150 return conversionSucceeded
151 ? ConvertGenericList(desiredType, input, ref conversionSucceeded)
152 : null;
154 else
156 return ConvertUsingTypeConverter(desiredType, input, ref conversionSucceeded);
159 catch(BindingException)
161 throw;
163 catch(Exception inner)
165 conversionSucceeded = false;
167 ThrowInformativeException(desiredType, input, inner);
168 return null;
172 private object ConvertNullable(Type desiredType, object input, ref bool conversionSucceeded)
174 Type underlyingType = Nullable.GetUnderlyingType(desiredType);
176 object value = Convert(underlyingType, input, out conversionSucceeded);
178 if (conversionSucceeded && value != null)
180 Type typeToConstruct = typeof(Nullable<>).MakeGenericType(underlyingType);
182 return Activator.CreateInstance(typeToConstruct, value);
184 else if (input != null)
186 conversionSucceeded = true;
188 else
190 conversionSucceeded = false;
193 return null;
196 private object ConvertGenericList(Type desiredType, object input, ref bool conversionSucceeded)
198 Type elemType = desiredType.GetGenericArguments()[0];
200 input = FixInputForMonoIfNeeded(elemType, input);
202 Type listType = typeof(List<>).MakeGenericType(elemType);
203 IList result = (IList) Activator.CreateInstance(listType);
204 Array values = input as Array;
206 bool elementConversionSucceeded;
208 for(int i = 0; i < values.Length; i++)
210 result.Add(Convert(elemType, values.GetValue(i), out elementConversionSucceeded));
212 // if at least one list element get converted
213 // we consider the conversion a success
214 if (elementConversionSucceeded && !conversionSucceeded)
216 conversionSucceeded = true;
220 return result;
223 /// <summary>
224 /// Fix for mod_mono issue where array values are passed as a comma seperated String.
225 /// </summary>
226 /// <param name="elemType"></param>
227 /// <param name="input"></param>
228 /// <returns></returns>
229 private static object FixInputForMonoIfNeeded(Type elemType, object input)
231 if (!input.GetType().IsArray)
233 if (input.GetType() == typeof(String))
235 input = NormalizeInput(input);
237 if (input.ToString() == String.Empty)
239 input = Array.CreateInstance(elemType, 0);
241 else
243 input = input.ToString().Split(',');
246 else
248 throw new BindingException("Cannot convert to collection of {0} from type {1}", elemType.FullName,
249 input.GetType().FullName);
253 return input;
256 private object ConvertGuid(object input, ref bool conversionSucceeded)
258 conversionSucceeded = true;
260 if (input == null)
262 conversionSucceeded = false;
263 return null;
266 String value = NormalizeInput(input);
268 if (value == String.Empty)
270 conversionSucceeded = true;
271 return null;
273 else
275 return new Guid(value);
279 private object ConvertDecimal(object input, ref bool conversionSucceeded)
281 conversionSucceeded = true;
283 if (input == null)
285 conversionSucceeded = false;
286 return null;
289 String value = NormalizeInput(input);
291 if (value == String.Empty)
293 conversionSucceeded = true;
294 return null;
296 else
298 return System.Convert.ToDecimal(value);
302 private object ConvertPrimitive(Type desiredType, object input, ref bool conversionSucceeded)
304 conversionSucceeded = true;
306 String value = NormalizeInput(input);
308 if (IsBool(desiredType))
310 return SpecialBoolConversion(value, input, ref conversionSucceeded);
312 else if (input == null)
314 conversionSucceeded = false;
315 return null;
317 else if (value == String.Empty)
319 conversionSucceeded = true;
320 return null;
322 else
324 return System.Convert.ChangeType(input, desiredType);
328 private static object SpecialBoolConversion(string value, object input, ref bool conversionSucceeded)
330 if (input == null)
332 conversionSucceeded = false;
333 return null;
335 else if (value == String.Empty)
337 conversionSucceeded = false;
338 return false;
340 else
342 if (value.IndexOf(',') != -1)
344 value = value.Substring(0, value.IndexOf(','));
347 bool performNumericConversion = false;
349 foreach(char c in value.ToCharArray())
351 if (Char.IsNumber(c))
353 performNumericConversion = true;
354 break;
358 if (performNumericConversion)
360 return System.Convert.ToBoolean(System.Convert.ToInt32(value));
362 else
364 return !(String.Compare("false", value, true) == 0);
369 private object ConvertToArray(Type desiredType, object input, ref bool conversionSucceeded)
371 Type elemType = desiredType.GetElementType();
373 input = FixInputForMonoIfNeeded(elemType, input);
375 Array values = input as Array;
376 Array result = Array.CreateInstance(elemType, values.Length);
378 for(int i = 0; i < values.Length; i++)
380 bool elementConversionSucceeded;
382 result.SetValue(Convert(elemType, values.GetValue(i), out elementConversionSucceeded), i);
384 // if at least one array element get converted
385 // we consider the conversion a success
386 if (elementConversionSucceeded && !conversionSucceeded)
388 conversionSucceeded = true;
392 return result;
395 private static bool IsBool(Type desiredType)
397 bool isBool = desiredType == typeof(Boolean);
399 if (!isBool)
401 isBool = desiredType == typeof(bool?);
404 return isBool;
407 private static object ConvertEnum(Type desiredType, object input, ref bool conversionSucceeded)
409 conversionSucceeded = true;
411 if (input == null)
413 conversionSucceeded = false;
414 return null;
417 String value = NormalizeInput(input);
419 if (value == String.Empty)
421 conversionSucceeded = false;
422 return null;
424 else
426 return Enum.Parse(desiredType, value, true);
430 private static object ConvertDate(object input, ref bool conversionSucceeded)
432 conversionSucceeded = true;
434 if (input == null)
436 conversionSucceeded = false;
437 return null;
440 String value = NormalizeInput(input);
442 if (value == String.Empty)
444 conversionSucceeded = false;
446 return null;
448 else
450 return DateTime.Parse(value);
454 /// <summary>
455 /// Support for types that specify a TypeConverter,
456 /// i.e.: NullableTypes
457 /// </summary>
458 private static object ConvertUsingTypeConverter(Type desiredType, object input, ref bool conversionSucceeded)
460 conversionSucceeded = true;
462 Type sourceType = (input != null ? input.GetType() : typeof(String));
463 TypeConverter conv = TypeDescriptor.GetConverter(desiredType);
465 if (conv != null && conv.CanConvertFrom(sourceType))
469 return conv.ConvertFrom(input);
471 catch(Exception ex)
473 String message = String.Format("Conversion error: " +
474 "Could not convert parameter with value '{0}' to {1}", input, desiredType);
476 throw new BindingException(message, ex);
479 else
481 String message = String.Format("Conversion error: " +
482 "Could not convert parameter with value '{0}' to {1}", input, desiredType);
484 throw new BindingException(message);
488 private static String NormalizeInput(object input)
490 if (input == null)
492 return String.Empty;
494 else
496 if (!input.GetType().IsArray)
498 return input.ToString().Trim();
500 else
502 Array array = (Array) input;
504 String[] stArray = new string[array.GetLength(0)];
506 for(int i = 0; i < stArray.Length; i++)
508 object itemVal = array.GetValue(i);
510 if (itemVal != null)
512 stArray[i] = itemVal.ToString();
514 else
516 stArray[i] = String.Empty;
520 return String.Join(",", stArray);
525 private static void ThrowInformativeException(Type desiredType, object input, Exception inner)
527 String message = String.Format("Conversion error: " +
528 "Could not convert parameter with value '{0}' to expected type {1}", input,
529 desiredType);
531 throw new BindingException(message, inner);