File: winforms\Managed\System\WinForms\Formatter.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="Formatter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms
{
    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Reflection;
    using System.Security;
    using System.Security.Permissions;
    
    internal class Formatter
    {
        static private Type stringType = typeof(String);
        static private Type booleanType = typeof(bool);
        static private Type checkStateType = typeof(CheckState);
        static private Object parseMethodNotFound = new Object();
        static private Object defaultDataSourceNullValue = System.DBNull.Value;
 
        /// <devdoc>
        ///
        /// Converts a binary value into a format suitable for display to the end user.
        /// Used when pushing a value from a back-end data source into a data-bound property on a control.
        ///
        /// The real conversion work happens inside FormatObjectInternal(). Before calling FormatObjectInternal(),
        /// we check for any use of nullable types or values (eg. Nullable<T>) and 'unwrap' them to get at the real 
        /// types and values, which are then used in the actual conversion. If the caller is expecting a nullable
        /// value back, we must also re-wrap the final result inside a nullable value before returning.
        ///
        /// </devdoc>
        public static object FormatObject(object value,
                                          Type targetType, 
                                          TypeConverter sourceConverter, 
                                          TypeConverter targetConverter, 
                                          string formatString, 
                                          IFormatProvider formatInfo, 
                                          object formattedNullValue,
                                          object dataSourceNullValue) {
            //
            // On the way in, see if value represents 'null' for this back-end field type, and substitute DBNull.
            // For most types, 'null' is actually represented by DBNull. But for a nullable type, its represented
            // by an instance of that type with no value. And for business objects it may be represented by a
            // simple null reference.
            //
 
            if (Formatter.IsNullData(value, dataSourceNullValue)) {
                value = System.DBNull.Value;
            }
 
            //
            // Strip away any use of nullable types (eg. Nullable<int>), leaving just the 'real' types
            //
 
            Type oldTargetType = targetType;
 
            targetType      = NullableUnwrap(targetType);
            sourceConverter = NullableUnwrap(sourceConverter);
            targetConverter = NullableUnwrap(targetConverter);
 
            bool isNullableTargetType = (targetType != oldTargetType);
 
            //
            // Call the 'real' method to perform the conversion
            //
 
            object result = FormatObjectInternal(value, targetType, sourceConverter, targetConverter, formatString, formatInfo, formattedNullValue);
 
            if (oldTargetType.IsValueType && result == null && !isNullableTargetType)
            {
                throw new FormatException(GetCantConvertMessage(value, targetType));
            }
            return result;
        }
 
        /// <devdoc>
        ///
        /// Converts a value into a format suitable for display to the end user.
        ///
        /// - Converts DBNull or null into a suitable formatted representation of 'null'
        /// - Performs some special-case conversions (eg. Boolean to CheckState)
        /// - Uses TypeConverters or IConvertible where appropriate
        /// - Throws a FormatException is no suitable conversion can be found
        ///
        /// </devdoc>
        private static object FormatObjectInternal(object value,
                                                   Type targetType, 
                                                   TypeConverter sourceConverter, 
                                                   TypeConverter targetConverter, 
                                                   string formatString, 
                                                   IFormatProvider formatInfo, 
                                                   object formattedNullValue) {
            if (value == System.DBNull.Value || value == null) {
                //
                // Convert DBNull to the formatted representation of 'null' (if possible)
                //
                if (formattedNullValue != null)
                {
                    return formattedNullValue;
                }
 
                //
                // Convert DBNull or null to a specific 'known' representation of null (otherwise fail)
                //
                if (targetType == stringType)
                {
                    return String.Empty;
                }
                
                if (targetType == checkStateType) {
                    return CheckState.Indeterminate;
                }
                
                // Just pass null through: if this is a value type, it's been unwrapped here, so we return null 
                // and the caller has to wrap if appropriate.
                return null; 
            }
 
            //
            // Special case conversions
            //
 
            if (targetType == stringType) {
                if (value is IFormattable && !String.IsNullOrEmpty(formatString)) {
                    return (value as IFormattable).ToString(formatString, formatInfo);
                }
            }
 
            //The converters for properties should take precedence.  Unfortunately, we don't know whether we have one.  Check vs. the 
            //type's TypeConverter.  We're punting the case where the property-provided converter is the same as the type's converter.
            Type sourceType = value.GetType();
            TypeConverter sourceTypeTypeConverter = TypeDescriptor.GetConverter(sourceType);
            if (sourceConverter != null && sourceConverter != sourceTypeTypeConverter && sourceConverter.CanConvertTo(targetType)) {
                return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
            }
 
            TypeConverter targetTypeTypeConverter = TypeDescriptor.GetConverter(targetType);
            if (targetConverter != null && targetConverter != targetTypeTypeConverter && targetConverter.CanConvertFrom(sourceType)) {
                return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
            }
 
            if (targetType == checkStateType) {
                if (sourceType == booleanType) {
                    return ((bool)value) ? CheckState.Checked : CheckState.Unchecked;
                } 
                else {
                    if (sourceConverter == null) {
                        sourceConverter = sourceTypeTypeConverter;
                    }
                    if (sourceConverter != null && sourceConverter.CanConvertTo(booleanType)) {
                        return (bool)sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, booleanType)
                            ? CheckState.Checked : CheckState.Unchecked;
                    }
                }
            }
 
            if (targetType.IsAssignableFrom(sourceType)) {
                return value;
            }
 
            //
            // If explicit type converters not provided, supply default ones instead
            //
 
            if (sourceConverter == null) {
                sourceConverter = sourceTypeTypeConverter;
            }
 
            if (targetConverter == null) {
                targetConverter = targetTypeTypeConverter;
            }
 
            //
            // Standardized conversions
            //
 
            if (sourceConverter != null && sourceConverter.CanConvertTo(targetType)) {
                return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
            }
            else if (targetConverter != null && targetConverter.CanConvertFrom(sourceType)) {
                return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
            }
            else if (value is IConvertible) {
                return ChangeType(value, targetType, formatInfo);
            }
 
            //
            // Fail if no suitable conversion found
            //
 
            throw new FormatException(GetCantConvertMessage(value, targetType));
        }
 
        /// <devdoc>
        ///
        /// Converts a value entered by the end user (through UI) into the corresponding binary value.
        /// Used when pulling input from a data-bound property on a control to store in a back-end data source.
        ///
        /// The real conversion work happens inside ParseObjectInternal(). Before calling ParseObjectInternal(),
        /// we check for any use of nullable types or values (eg. Nullable<T>) and 'unwrap' them to get at the real 
        /// types and values, which are then used in the actual conversion. If the caller is expecting a nullable
        /// value back, we must also re-wrap the final result inside a nullable value before returning.
        ///
        /// </devdoc>
        public static object ParseObject(object value, 
                                         Type targetType, 
                                         Type sourceType, 
                                         TypeConverter targetConverter, 
                                         TypeConverter sourceConverter, 
                                         IFormatProvider formatInfo, 
                                         object formattedNullValue,
                                         object dataSourceNullValue) {
            //
            // Strip away any use of nullable types (eg. Nullable<int>), leaving just the 'real' types
            //
 
            Type oldTargetType = targetType;
 
            sourceType      = NullableUnwrap(sourceType);
            targetType      = NullableUnwrap(targetType);
            sourceConverter = NullableUnwrap(sourceConverter);
            targetConverter = NullableUnwrap(targetConverter);
 
            bool isNullableTargetType = (targetType != oldTargetType);
 
            //
            // Call the 'real' method to perform the conversion
            //
 
            object result = ParseObjectInternal(value, targetType, sourceType, targetConverter, sourceConverter, formatInfo, formattedNullValue);
 
            //
            // On the way out, substitute DBNull with the appropriate representation of 'null' for the final target type.
            // For most types, this is just DBNull. But for a nullable type, its an instance of that type with no value.
            //
 
            if (result == System.DBNull.Value) {
                return Formatter.NullData(oldTargetType, dataSourceNullValue);
            }
 
            return result;
        }
 
        /// <devdoc>
        ///
        /// Converts a value entered by the end user (through UI) into the corresponding binary value.
        ///
        /// - Converts formatted representations of 'null' into DBNull
        /// - Performs some special-case conversions (eg. CheckState to Boolean)
        /// - Uses TypeConverters or IConvertible where appropriate
        /// - Throws a FormatException is no suitable conversion can be found
        ///
        /// </devdoc>
        private static object ParseObjectInternal(object value, 
                                                  Type targetType, 
                                                  Type sourceType, 
                                                  TypeConverter targetConverter, 
                                                  TypeConverter sourceConverter, 
                                                  IFormatProvider formatInfo, 
                                                  object formattedNullValue) {
            //
            // Convert the formatted representation of 'null' to DBNull (if possible)
            //
 
            if (EqualsFormattedNullValue(value, formattedNullValue, formatInfo) || value == System.DBNull.Value) {
                return System.DBNull.Value;
            }
 
            //
            // Special case conversions
            //
 
            TypeConverter targetTypeTypeConverter = TypeDescriptor.GetConverter(targetType);
            if (targetConverter != null && targetTypeTypeConverter != targetConverter && targetConverter.CanConvertFrom(sourceType)) {
                return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
            }
 
            TypeConverter sourceTypeTypeConverter = TypeDescriptor.GetConverter(sourceType);
            if (sourceConverter != null && sourceTypeTypeConverter != sourceConverter && sourceConverter.CanConvertTo(targetType)) {
                return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
            }
 
            if (value is string) {
                // If target type has a suitable Parse method, use that to parse strings
                object parseResult = InvokeStringParseMethod(value, targetType, formatInfo);
                if (parseResult != parseMethodNotFound) {
                    return parseResult;
                }
            }
            else if (value is CheckState) {
                CheckState state = (CheckState)value;
                if (state == CheckState.Indeterminate) {
                    return DBNull.Value;
                }
                // Explicit conversion from CheckState to Boolean
                if (targetType == booleanType) {
                    return (state == CheckState.Checked);
                }
                if (targetConverter == null) {
                    targetConverter = targetTypeTypeConverter;
                }
                if (targetConverter != null && targetConverter.CanConvertFrom(booleanType)) {
                    return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), state == CheckState.Checked);
                }
            }
            else if (value != null && targetType.IsAssignableFrom(value.GetType())) {
                // If value is already of a compatible type, just go ahead and use it
                return value;
            }
 
            //
            // If explicit type converters not provided, supply default ones instead
            //
 
            if (targetConverter == null) {
                targetConverter = targetTypeTypeConverter;
            }
 
            if (sourceConverter == null) {
                sourceConverter = sourceTypeTypeConverter;
            }
 
            //
            // Standardized conversions
            //
 
            if (targetConverter != null && targetConverter.CanConvertFrom(sourceType)) {
                return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
            }
            else if (sourceConverter != null && sourceConverter.CanConvertTo(targetType)) {
                return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
            }
            else if (value is IConvertible) {
                return ChangeType(value, targetType, formatInfo);
            }
 
            //
            // Fail if no suitable conversion found
            //
 
            throw new FormatException(GetCantConvertMessage(value, targetType));
        }
 
        /// <devdoc>
        /// Converts a value to the specified type using Convert.ChangeType()
        /// </devdoc>
        private static object ChangeType(object value, Type type, IFormatProvider formatInfo) {
            try {
                if (formatInfo == null) {
                    formatInfo = CultureInfo.CurrentCulture;
                }
 
                return Convert.ChangeType(value, type, formatInfo);
            }
            catch (InvalidCastException ex) {
                throw new FormatException(ex.Message, ex);
            }
        }
 
        /// <devdoc>
        /// Indicates whether the specified value matches the display-formatted representation of 'null data' for a given binding.
        /// </devdoc>
        private static bool EqualsFormattedNullValue(object value, object formattedNullValue, IFormatProvider formatInfo) {
            string formattedNullValueStr = formattedNullValue as string;
            string valueStr = value as string;
            if (formattedNullValueStr != null && valueStr != null) {
                // Use same optimization as in WindowsFormsUtils.SafeCompareStrings(...). This addresses bug DevDiv Bugs 110336.
                if (formattedNullValueStr.Length != valueStr.Length) {
                    return false;
                }
                // Always do a case insensitive comparison for strings
                return String.Compare(valueStr, formattedNullValueStr, true, GetFormatterCulture(formatInfo)) == 0;
            }
            else {
                // Otherwise perform default comparison based on object types
                return Object.Equals(value, formattedNullValue);
            }
        }
 
        /// <devdoc>
        /// Returns the FormatException message used when formatting/parsing fails to find any suitable conversion
        /// </devdoc>
        private static string GetCantConvertMessage(object value, Type targetType) {
            string stringResId = (value == null) ? SR.Formatter_CantConvertNull : SR.Formatter_CantConvert;
            return String.Format(CultureInfo.CurrentCulture, SR.GetString(stringResId), value, targetType.Name);
        }
 
        /// <devdoc>
        /// Determines the correct culture to use during formatting and parsing
        /// </devdoc>
        private static CultureInfo GetFormatterCulture(IFormatProvider formatInfo) {
            if (formatInfo is CultureInfo) {
                return formatInfo as CultureInfo;
            }
            else {
                return CultureInfo.CurrentCulture;
            }
        }
 
        /// <devdoc>
        /// Converts a value to the specified type using best Parse() method on that type
        /// </devdoc>
        public static object InvokeStringParseMethod(object value, Type targetType, IFormatProvider formatInfo) {
            try {
                MethodInfo mi;
 
                mi = targetType.GetMethod("Parse", 
                                        BindingFlags.Public | BindingFlags.Static, 
                                        null, 
                                        new Type[] {stringType, typeof(System.Globalization.NumberStyles), typeof(System.IFormatProvider)}, 
                                        null);
                if (mi != null) {
                    return mi.Invoke(null, new object [] {(string) value, NumberStyles.Any, formatInfo});
                }
 
                mi = targetType.GetMethod("Parse",
                                        BindingFlags.Public | BindingFlags.Static, 
                                        null, 
                                        new Type[] {stringType, typeof(System.IFormatProvider)}, 
                                        null);
                if (mi != null) {
                    return mi.Invoke(null, new object [] {(string) value, formatInfo});
                }
 
                mi = targetType.GetMethod("Parse",
                                        BindingFlags.Public | BindingFlags.Static, 
                                        null, 
                                        new Type[] {stringType}, 
                                        null);
                if (mi != null) {
                    return mi.Invoke(null, new object [] {(string) value});
                }
 
                return parseMethodNotFound;
            }
            catch (TargetInvocationException ex) {
                throw new FormatException(ex.InnerException.Message, ex.InnerException);
            }
        }
 
        /// <devdoc>
        /// Indicates whether a given value represents 'null' for data source fields of the same type.
        /// </devdoc>
        public static bool IsNullData(object value, object dataSourceNullValue) {
            return value == null ||
                   value == System.DBNull.Value ||
                   Object.Equals(value, NullData(value.GetType(), dataSourceNullValue));
        }
 
        /// <devdoc>
        /// Returns the default representation of 'null' for a given data source field type.
        /// </devdoc>
        public static object NullData(Type type, object dataSourceNullValue) {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
                // For nullable types, null is represented by an instance of that type with no assigned value.
                // The value could also be DBNull.Value (the default for dataSourceNullValue).
                if (dataSourceNullValue == null || dataSourceNullValue == DBNull.Value)
                {
                    // We don't have a special value that represents null on the data source:
                    // use the Nullable<T>'s representation
                    return null;
                }
                else
                {
                    return dataSourceNullValue;
                }
            }
            else {
                // For all other types, the default representation of null is defined by
                // the caller (this will usually be System.DBNull.Value for ADO.NET data
                // sources, or a null reference for 'business object' data sources).
                return dataSourceNullValue;
            }
        }
 
        /// <devdoc>
        /// Extract the inner type from a nullable type
        /// </devdoc>
        private static Type NullableUnwrap(Type type) {
            if (type == stringType) // ...performance optimization for the most common case
                return stringType;
 
 
            Type underlyingType = Nullable.GetUnderlyingType(type);
            return underlyingType ?? type;
        }
 
        /// <devdoc>
        /// Extract the inner type converter from a nullable type converter
        /// </devdoc>
        private static TypeConverter NullableUnwrap(TypeConverter typeConverter) {
            NullableConverter nullableConverter = typeConverter as NullableConverter;
            return (nullableConverter != null) ? nullableConverter.UnderlyingTypeConverter : typeConverter;
        }
 
        public static object GetDefaultDataSourceNullValue(Type type) {
            return (type != null && !type.IsValueType) ? null : defaultDataSourceNullValue;
        }
 
    }
}