SPRNET-970 - TypeConversionUtils should not throw TypeMismatchException on null value for System.Nullable

This commit is contained in:
markpollack
2008-08-13 21:49:31 +00:00
parent 5722a6515f
commit d2fa8e510d
3 changed files with 260 additions and 205 deletions

View File

@@ -1,180 +1,186 @@
#region License
/*
* Copyright 2002-2004 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.ComponentModel;
using Spring.Util;
#endregion
namespace Spring.Core.TypeConversion
{
/// <summary>
/// Utility methods that are used to convert objects from one type into another.
/// </summary>
/// <author>Aleksandar Seovic</author>
public class TypeConversionUtils
{
/// <summary>
/// Convert the value to the required <see cref="System.Type"/> (if necessary from a string).
/// </summary>
/// <param name="newValue">The proposed change value.</param>
/// <param name="requiredType">
/// The <see cref="System.Type"/> we must convert to.
/// </param>
/// <param name="propertyName">Property name, used for error reporting purposes...</param>
/// <exception cref="Spring.Objects.ObjectsException">
/// If there is an internal error.
/// </exception>
/// <returns>The new value, possibly the result of type conversion.</returns>
public static object ConvertValueIfNecessary(Type requiredType, object newValue, string propertyName)
{
if (newValue != null)
{
// if it is assignable, return the value right away
if (IsAssignableFrom(newValue, requiredType))
{
return newValue;
}
// if required type is an array, convert all the elements
if (requiredType != null && requiredType.IsArray)
{
// convert individual elements to array elements
Type componentType = requiredType.GetElementType();
if (newValue is ICollection)
{
ICollection elements = (ICollection) newValue;
return ToArrayWithTypeConversion(componentType, elements, propertyName);
}
else if (newValue is string)
{
if (requiredType.Equals(typeof(char[])))
{
return ((string) newValue).ToCharArray();
}
else
{
string[] elements = StringUtils.CommaDelimitedListToStringArray((string) newValue);
return ToArrayWithTypeConversion(componentType, elements, propertyName);
}
}
else if (!newValue.GetType().IsArray)
#region License
/*
* Copyright 2002-2004 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.ComponentModel;
using Spring.Util;
#endregion
namespace Spring.Core.TypeConversion
{
/// <summary>
/// Utility methods that are used to convert objects from one type into another.
/// </summary>
/// <author>Aleksandar Seovic</author>
public class TypeConversionUtils
{
/// <summary>
/// Convert the value to the required <see cref="System.Type"/> (if necessary from a string).
/// </summary>
/// <param name="newValue">The proposed change value.</param>
/// <param name="requiredType">
/// The <see cref="System.Type"/> we must convert to.
/// </param>
/// <param name="propertyName">Property name, used for error reporting purposes...</param>
/// <exception cref="Spring.Objects.ObjectsException">
/// If there is an internal error.
/// </exception>
/// <returns>The new value, possibly the result of type conversion.</returns>
public static object ConvertValueIfNecessary(Type requiredType, object newValue, string propertyName)
{
if (newValue != null)
{
// if it is assignable, return the value right away
if (IsAssignableFrom(newValue, requiredType))
{
return newValue;
}
// if required type is an array, convert all the elements
if (requiredType != null && requiredType.IsArray)
{
// convert individual elements to array elements
Type componentType = requiredType.GetElementType();
if (newValue is ICollection)
{
// A plain value: convert it to an array with a single component.
Array result = Array.CreateInstance(componentType, 1);
object val = ConvertValueIfNecessary(componentType, newValue, propertyName);
result.SetValue(val, 0);
return result;
}
}
// try to convert using type converter
try
{
TypeConverter typeConverter = TypeConverterRegistry.GetConverter(requiredType);
if (typeConverter != null && typeConverter.CanConvertFrom(newValue.GetType()))
{
try
{
newValue = typeConverter.ConvertFrom(newValue);
}
catch
{
if (newValue is string)
{
newValue = typeConverter.ConvertFromInvariantString((string)newValue);
}
}
}
else
{
typeConverter = TypeConverterRegistry.GetConverter(newValue.GetType());
if (typeConverter != null && typeConverter.CanConvertTo(requiredType))
{
newValue = typeConverter.ConvertTo(newValue, requiredType);
}
else
{
// look if it's an enum
if (requiredType != null
&& requiredType.IsEnum
&& (!(newValue is float)
&& (!(newValue is double))))
{
// convert numeric value into enum's underlying type
Type numericType = Enum.GetUnderlyingType(requiredType);
newValue = Convert.ChangeType(newValue, numericType);
if (Enum.IsDefined(requiredType, newValue))
{
newValue = Enum.ToObject(requiredType, newValue);
}
else
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
else if (newValue is IConvertible)
{
// last resort - try ChangeType
newValue = Convert.ChangeType(newValue, requiredType);
}
else
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
}
}
catch (Exception ex)
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType, ex);
}
if (newValue == null)
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
return newValue;
}
private static object ToArrayWithTypeConversion(Type componentType, ICollection elements, string propertyName)
{
ICollection elements = (ICollection) newValue;
return ToArrayWithTypeConversion(componentType, elements, propertyName);
}
else if (newValue is string)
{
if (requiredType.Equals(typeof(char[])))
{
return ((string) newValue).ToCharArray();
}
else
{
string[] elements = StringUtils.CommaDelimitedListToStringArray((string) newValue);
return ToArrayWithTypeConversion(componentType, elements, propertyName);
}
}
else if (!newValue.GetType().IsArray)
{
// A plain value: convert it to an array with a single component.
Array result = Array.CreateInstance(componentType, 1);
object val = ConvertValueIfNecessary(componentType, newValue, propertyName);
result.SetValue(val, 0);
return result;
}
}
// try to convert using type converter
try
{
TypeConverter typeConverter = TypeConverterRegistry.GetConverter(requiredType);
if (typeConverter != null && typeConverter.CanConvertFrom(newValue.GetType()))
{
try
{
newValue = typeConverter.ConvertFrom(newValue);
}
catch
{
if (newValue is string)
{
newValue = typeConverter.ConvertFromInvariantString((string)newValue);
}
}
}
else
{
typeConverter = TypeConverterRegistry.GetConverter(newValue.GetType());
if (typeConverter != null && typeConverter.CanConvertTo(requiredType))
{
newValue = typeConverter.ConvertTo(newValue, requiredType);
}
else
{
// look if it's an enum
if (requiredType != null
&& requiredType.IsEnum
&& (!(newValue is float)
&& (!(newValue is double))))
{
// convert numeric value into enum's underlying type
Type numericType = Enum.GetUnderlyingType(requiredType);
newValue = Convert.ChangeType(newValue, numericType);
if (Enum.IsDefined(requiredType, newValue))
{
newValue = Enum.ToObject(requiredType, newValue);
}
else
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
else if (newValue is IConvertible)
{
// last resort - try ChangeType
newValue = Convert.ChangeType(newValue, requiredType);
}
else
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
}
}
catch (Exception ex)
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType, ex);
}
#if NET_2_0
if (newValue == null
&& (requiredType == null
|| !Type.GetType("System.Nullable`1").Equals(requiredType.GetGenericTypeDefinition())))
#else
if (newValue == null)
#endif
{
throw new TypeMismatchException(
CreatePropertyChangeEventArgs(propertyName, null, newValue), requiredType);
}
}
return newValue;
}
private static object ToArrayWithTypeConversion(Type componentType, ICollection elements, string propertyName)
{
Array destination = Array.CreateInstance(componentType, elements.Count);
int i = 0;
int i = 0;
foreach (object element in elements)
{
object value = ConvertValueIfNecessary(componentType, element, BuildIndexedPropertyName(propertyName, i));
destination.SetValue(value, i);
i++;
}
return destination;
}
return destination;
}
private static string BuildIndexedPropertyName(string propertyName, int index)
@@ -182,39 +188,39 @@ namespace Spring.Core.TypeConversion
return (propertyName != null ?
propertyName + "[" + index + "]":
null);
}
private static bool IsAssignableFrom(object newValue, Type requiredType)
{
if (newValue is MarshalByRefObject)
{
//TODO see what type of type checking can be done. May need to
//preserve information when proxy was created by SaoServiceExporter.
return true;
}
if (requiredType == null)
{
return false;
}
return requiredType.IsAssignableFrom(newValue.GetType());
}
/// <summary>
/// Utility method to create a property change event.
/// </summary>
/// <param name="fullPropertyName">
/// The full name of the property that has changed.
/// </param>
/// <param name="oldValue">The property old value</param>
/// <param name="newValue">The property new value</param>
/// <returns>
/// A new <see cref="Spring.Core.PropertyChangeEventArgs"/>.
/// </returns>
private static PropertyChangeEventArgs CreatePropertyChangeEventArgs(string fullPropertyName, object oldValue,
object newValue)
{
return new PropertyChangeEventArgs(fullPropertyName, oldValue, newValue);
}
}
}
private static bool IsAssignableFrom(object newValue, Type requiredType)
{
if (newValue is MarshalByRefObject)
{
//TODO see what type of type checking can be done. May need to
//preserve information when proxy was created by SaoServiceExporter.
return true;
}
if (requiredType == null)
{
return false;
}
return requiredType.IsAssignableFrom(newValue.GetType());
}
/// <summary>
/// Utility method to create a property change event.
/// </summary>
/// <param name="fullPropertyName">
/// The full name of the property that has changed.
/// </param>
/// <param name="oldValue">The property old value</param>
/// <param name="newValue">The property new value</param>
/// <returns>
/// A new <see cref="Spring.Core.PropertyChangeEventArgs"/>.
/// </returns>
private static PropertyChangeEventArgs CreatePropertyChangeEventArgs(string fullPropertyName, object oldValue,
object newValue)
{
return new PropertyChangeEventArgs(fullPropertyName, oldValue, newValue);
}
}
}

View File

@@ -0,0 +1,48 @@
#region License
/*
* Copyright <20> 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using NUnit.Framework;
#endregion
namespace Spring.Core.TypeConversion
{
/// <summary>
/// This class contains tests for TypeConversionUtils
/// </summary>
/// <author>Mark Pollack</author>
[TestFixture]
public class TypeConversionUtilsTests
{
#if NET_2_0
[Test]
public void NullAbleTest()
{
object o = TypeConversionUtils.ConvertValueIfNecessary(typeof(DateTime?), "", "bla");
Assert.IsNull(o);
}
#endif
}
}

View File

@@ -260,6 +260,7 @@
<Compile Include="Core\TypeConversion\StreamConverterTests.cs" />
<Compile Include="Core\TypeConversion\StringArrayConverterTests.cs" />
<Compile Include="Core\TypeConversion\TimeSpanConverterTests.cs" />
<Compile Include="Core\TypeConversion\TypeConversionUtilsTests.cs" />
<Compile Include="Core\TypeConversion\TypeConverterRegistryTests.cs" />
<Compile Include="Core\TypeConversion\UriConverterTests.cs" />
<Compile Include="Core\TypeResolution\CachedTypeResolverTests.cs" />