//------------------------------------------------------------------------------
// <copyright file="TypeConverter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

/*
 */
namespace System.ComponentModel {
    using Microsoft.Win32;
    using System.Collections;
    using System.Configuration;
    using System.ComponentModel.Design.Serialization;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Runtime.Serialization.Formatters;
    using System.Security.Permissions;

    /// <devdoc>
    ///    <para>Converts the value of an object into a different data type.</para>
    /// </devdoc>
    [HostProtection(SharedState = true)]
    [System.Runtime.InteropServices.ComVisible(true)]
    public class TypeConverter {

        private const string s_UseCompatibleTypeConverterBehavior = "UseCompatibleTypeConverterBehavior";
        private static volatile bool useCompatibleTypeConversion = false;
        private static volatile bool firstLoadAppSetting = true;
        private static object loadAppSettingLock = new Object();

        private static bool UseCompatibleTypeConversion {
            get {
#if !MONO
                if (firstLoadAppSetting) {
                    lock (loadAppSettingLock) {
                        if (firstLoadAppSetting) {
                            string useCompatibleConfig = ConfigurationManager.AppSettings[s_UseCompatibleTypeConverterBehavior];

                            try {
                                if (!String.IsNullOrEmpty(useCompatibleConfig)) {
                                    useCompatibleTypeConversion = bool.Parse(useCompatibleConfig.Trim());
                                }
                            }
                            catch {
                                // we get any exception, then eat out the exception, and use the new TypeConverter.
                                useCompatibleTypeConversion = false;
                            }

                            firstLoadAppSetting = false;
                        }
                    }
                }
#endif
                return useCompatibleTypeConversion;
            }
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether this converter can convert an object in the
        ///       given source type to the native type of the converter.</para>
        /// </devdoc>
        public bool CanConvertFrom(Type sourceType) {
            return CanConvertFrom(null, sourceType);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether this converter can
        ///       convert an object in the given source type to the native type of the converter
        ///       using the context.</para>
        /// </devdoc>
        public virtual bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            if (sourceType == typeof(InstanceDescriptor)) {
                return true;
            }
            return false;
        }
        
        /// <devdoc>
        ///    <para>Gets a value indicating whether this converter can
        ///       convert an object to the given destination type using the context.</para>
        /// </devdoc>
        public bool CanConvertTo(Type destinationType) {
            return CanConvertTo(null, destinationType);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether this converter can
        ///       convert an object to the given destination type using the context.</para>
        /// </devdoc>
        public virtual bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
            return (destinationType == typeof(string));
        }

        /// <devdoc>
        ///    <para>Converts the given value
        ///       to the converter's native type.</para>
        /// </devdoc>
        public object ConvertFrom(object value) {
            return ConvertFrom(null, CultureInfo.CurrentCulture, value);
        }

        /// <devdoc>
        ///    <para>Converts the given object to the converter's native type.</para>
        /// </devdoc>
        public virtual object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
            InstanceDescriptor id = value as InstanceDescriptor;
            if (id != null) {
                return id.Invoke();
            }
            throw GetConvertFromException(value);
        }

        /// <devdoc>
        ///    Converts the given string to the converter's native type using the invariant culture.
        /// </devdoc>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public object ConvertFromInvariantString(string text) {
            return ConvertFromString(null, CultureInfo.InvariantCulture, text);
        }

        /// <devdoc>
        ///    Converts the given string to the converter's native type using the invariant culture.
        /// </devdoc>
        public object ConvertFromInvariantString(ITypeDescriptorContext context, string text) {
            return ConvertFromString(context, CultureInfo.InvariantCulture, text);
        }

        /// <devdoc>
        ///    <para>Converts the specified text into an object.</para>
        /// </devdoc>
        public object ConvertFromString(string text) {
            return ConvertFrom(null, null, text);
        }

        /// <devdoc>
        ///    <para>Converts the specified text into an object.</para>
        /// </devdoc>
        public object ConvertFromString(ITypeDescriptorContext context, string text) {
            return ConvertFrom(context, CultureInfo.CurrentCulture, text);
        }

        /// <devdoc>
        ///    <para>Converts the specified text into an object.</para>
        /// </devdoc>
        public object ConvertFromString(ITypeDescriptorContext context, CultureInfo culture, string text) {
            return ConvertFrom(context, culture, text);
        }

        /// <devdoc>
        ///    <para>Converts the given
        ///       value object to the specified destination type using the arguments.</para>
        /// </devdoc>
        public object ConvertTo(object value, Type destinationType) {
            return ConvertTo(null, null, value, destinationType);
        }

        /// <devdoc>
        ///    <para>Converts the given value object to
        ///       the specified destination type using the specified context and arguments.</para>
        /// </devdoc>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] // keep CultureInfo
        public virtual object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
            if (destinationType == null) {
                throw new ArgumentNullException("destinationType");
            }

            if (destinationType == typeof(string)) {
                if (value == null) {
                    return String.Empty;
                }

                // Pre-whidbey we just did a ToString() here.  To minimize the chance of a breaking change we
                // still send requests for the CurrentCulture to ToString() (which should return the same).
                if(culture != null && culture != CultureInfo.CurrentCulture) {
                    // VSWhidbey 75433 - If the object is IFormattable, use this interface to convert to string
                    // so we use the specified culture rather than the CurrentCulture like object.ToString() does.
                    IFormattable formattable = value as IFormattable;
                    if(formattable != null) {
                        return formattable.ToString(/* format = */ null, /* formatProvider = */ culture);
                    }
                }
                return value.ToString();
            }
            throw GetConvertToException(value, destinationType);
        }

        /// <devdoc>
        ///    <para>Converts the specified value to a culture-invariant string representation.</para>
        /// </devdoc>
        public string ConvertToInvariantString(object value) {
            return ConvertToString(null, CultureInfo.InvariantCulture, value);
        }

        /// <devdoc>
        ///    <para>Converts the specified value to a culture-invariant string representation.</para>
        /// </devdoc>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public string ConvertToInvariantString(ITypeDescriptorContext context, object value) {
            return ConvertToString(context, CultureInfo.InvariantCulture, value);
        }

        /// <devdoc>
        ///    <para>Converts the specified value to a string representation.</para>
        /// </devdoc>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public string ConvertToString(object value) {
            return (string)ConvertTo(null, CultureInfo.CurrentCulture, value, typeof(string));
        }

        /// <devdoc>
        ///    <para>Converts the specified value to a string representation.</para>
        /// </devdoc>
        public string ConvertToString(ITypeDescriptorContext context, object value) {
            return (string)ConvertTo(context, CultureInfo.CurrentCulture, value, typeof(string));
        }

        /// <devdoc>
        ///    <para>Converts the specified value to a string representation.</para>
        /// </devdoc>
        public string ConvertToString(ITypeDescriptorContext context, CultureInfo culture, object value) {
            return (string)ConvertTo(context, culture, value, typeof(string));
        }

        /// <devdoc>
        /// <para>Re-creates an <see cref='System.Object'/> given a set of property values for the object.</para>
        /// </devdoc>
        public object CreateInstance(IDictionary propertyValues) {
            return CreateInstance(null, propertyValues);
        }

        /// <devdoc>
        /// <para>Re-creates an <see cref='System.Object'/> given a set of property values for the
        ///    object.</para>
        /// </devdoc>
        public virtual object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) {
            return null;
        }

        /// <devdoc>
        ///    <para>
        ///       Gets a suitable exception to throw when a conversion cannot
        ///       be performed.
        ///    </para>
        /// </devdoc>
        protected Exception GetConvertFromException(object value) {
            string valueTypeName;

            if (value == null) {
                valueTypeName = SR.GetString(SR.ToStringNull);
            }
            else {
                valueTypeName = value.GetType().FullName;
            }

            throw new NotSupportedException(SR.GetString(SR.ConvertFromException, GetType().Name, valueTypeName));
        }

        /// <devdoc>
        ///    <para>Retrieves a suitable exception to throw when a conversion cannot
        ///       be performed.</para>
        /// </devdoc>
        protected Exception GetConvertToException(object value, Type destinationType) {
            string valueTypeName;

            if (value == null) {
                valueTypeName = SR.GetString(SR.ToStringNull);
            }
            else {
                valueTypeName = value.GetType().FullName;
            }

            throw new NotSupportedException(SR.GetString(SR.ConvertToException, GetType().Name, valueTypeName, destinationType.FullName));
        }
        
        /// <devdoc>
        ///    <para>Gets a value indicating whether changing a value on this 
        ///       object requires a call to <see cref='System.ComponentModel.TypeConverter.CreateInstance'/>
        ///       to create a new value.</para>
        /// </devdoc>
        public bool GetCreateInstanceSupported() {
            return GetCreateInstanceSupported(null);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether changing a value on this object requires a 
        ///       call to <see cref='System.ComponentModel.TypeConverter.CreateInstance'/> to create a new value,
        ///       using the specified context.</para>
        /// </devdoc>
        public virtual bool GetCreateInstanceSupported(ITypeDescriptorContext context) {
            return false;
        }

        /// <devdoc>
        ///    <para>Gets a collection of properties for the type of array specified by the value
        ///       parameter.</para>
        /// </devdoc>
        public PropertyDescriptorCollection GetProperties(object value) {
            return GetProperties(null, value);
        }

        /// <devdoc>
        ///    <para>Gets a collection of
        ///       properties for the type of array specified by the value parameter using the specified
        ///       context.</para>
        /// </devdoc>
        public PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value) {
            return GetProperties(context, value, new Attribute[] {BrowsableAttribute.Yes});
        }  
        
        /// <devdoc>
        ///    <para>Gets a collection of properties for
        ///       the type of array specified by the value parameter using the specified context and
        ///       attributes.</para>
        /// </devdoc>
        public virtual PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
            return null;
        }
       
        /// <devdoc>
        ///    <para>
        ///       Gets a value indicating whether this object supports properties.
        ///    </para>
        /// </devdoc>
        public bool GetPropertiesSupported() {
            return GetPropertiesSupported(null);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating
        ///       whether this object supports properties using the
        ///       specified context.</para>
        /// </devdoc>
        public virtual bool GetPropertiesSupported(ITypeDescriptorContext context) {
            return false;
        }
        
        /// <devdoc>
        ///    <para> Gets a collection of standard values for the data type this type
        ///       converter is designed for.</para>
        /// </devdoc>
        public ICollection GetStandardValues() {
            return GetStandardValues(null);
        }

        /// <devdoc>
        ///    <para>Gets a collection of standard values for the data type this type converter is
        ///       designed for.</para>
        /// </devdoc>
        public virtual StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) {
            return null;
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether the collection of standard values returned from
        ///    <see cref='System.ComponentModel.TypeConverter.GetStandardValues'/> is an exclusive list. </para>
        /// </devdoc>
        public bool GetStandardValuesExclusive() {
            return GetStandardValuesExclusive(null);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating whether the collection of standard values returned from
        ///    <see cref='System.ComponentModel.TypeConverter.GetStandardValues'/> is an exclusive 
        ///       list of possible values, using the specified context.</para>
        /// </devdoc>
        public virtual bool GetStandardValuesExclusive(ITypeDescriptorContext context) {
            return false;
        }

        /// <devdoc>
        ///    <para>
        ///       Gets a value indicating whether this object supports a standard set of values
        ///       that can be picked from a list.
        ///    </para>
        /// </devdoc>
        public bool GetStandardValuesSupported() {
            return GetStandardValuesSupported(null);
        }

        /// <devdoc>
        ///    <para>Gets a value indicating
        ///       whether this object
        ///       supports a standard set of values that can be picked
        ///       from a list using the specified context.</para>
        /// </devdoc>
        public virtual bool GetStandardValuesSupported(ITypeDescriptorContext context) {
            return false;
        }
        
        /// <devdoc>
        ///    <para>
        ///       Gets
        ///       a value indicating whether the given value object is valid for this type.
        ///    </para>
        /// </devdoc>
        public bool IsValid(object value) {
            return IsValid(null, value);
        }

        /// <devdoc>
        ///    <para>Gets
        ///       a value indicating whether the given value object is valid for this type.</para>
        /// </devdoc>
        public virtual bool IsValid(ITypeDescriptorContext context, object value) {
            if (UseCompatibleTypeConversion) {
                return true;
            }

            bool isValid = true;
            try {
                // Because null doesn't have a type, so we couldn't pass this to CanConvertFrom.
                // Meanwhile, we couldn't silence null value here, such as type converter like
                // NullableConverter would consider null value as a valid value.
                if (value == null || CanConvertFrom(context, value.GetType())) {
                    ConvertFrom(context, CultureInfo.InvariantCulture, value);
                }
                else {
                    isValid = false;
                }
            }
            catch {
                isValid = false;
            }

            return isValid;
        }
        
        /// <devdoc>
        ///    <para>Sorts a collection of properties.</para>
        /// </devdoc>
        protected PropertyDescriptorCollection SortProperties(PropertyDescriptorCollection props, string[] names) {
            props.Sort(names);
            return props;
        }

        /// <devdoc>
        ///    <para>
        ///       An <see langword='abstract '/>
        ///       class that provides
        ///       properties for objects that do not have
        ///       properties.
        ///    </para>
        /// </devdoc>
        protected abstract class SimplePropertyDescriptor : PropertyDescriptor {
            private Type   componentType;
            private Type   propertyType;
        

            /// <devdoc>
            ///    <para>
            ///       Initializes a new instance of the <see cref='System.ComponentModel.TypeConverter.SimplePropertyDescriptor'/>
            ///       class.
            ///    </para>
            /// </devdoc>
            protected SimplePropertyDescriptor(Type componentType, string name, Type propertyType) : this(componentType, name, propertyType, new Attribute[0]) {
            }
            
            /// <devdoc>
            ///    <para>
            ///       Initializes a new instance of the <see cref='System.ComponentModel.TypeConverter.SimplePropertyDescriptor'/> class.
            ///    </para>
            /// </devdoc>
            protected SimplePropertyDescriptor(Type componentType, string name, Type propertyType, Attribute[] attributes) : base(name, attributes) {
                this.componentType = componentType;
                this.propertyType = propertyType;
            }

            /// <devdoc>
            ///    <para>
            ///       Gets the type of the component this property description
            ///       is bound to.
            ///    </para>
            /// </devdoc>
            public override Type ComponentType {
                get {
                    return componentType;
                }
            }
                
            /// <devdoc>
            ///    <para>
            ///       Gets a
            ///       value indicating whether this property is read-only.
            ///    </para>
            /// </devdoc>
            public override bool IsReadOnly {
                get {
                    return Attributes.Contains(ReadOnlyAttribute.Yes);
                }
            }
    
            /// <devdoc>
            ///    <para>
            ///       Gets the type of the property.
            ///    </para>
            /// </devdoc>
            public override Type PropertyType {
                get {
                    return propertyType;
                }
            }
            
            /// <devdoc>
            ///    <para>Gets a value indicating whether resetting the component 
            ///       will change the value of the component.</para>
            /// </devdoc>
            public override bool CanResetValue(object component) {
                DefaultValueAttribute attr = (DefaultValueAttribute)Attributes[typeof(DefaultValueAttribute)];
                if (attr == null) {
                    return false;
                }
                return (attr.Value.Equals(GetValue(component)));
            }
            
            /// <devdoc>
            ///    <para>Resets the value for this property
            ///       of the component.</para>
            /// </devdoc>
            public override void ResetValue(object component) {
                DefaultValueAttribute attr = (DefaultValueAttribute)Attributes[typeof(DefaultValueAttribute)];
                if (attr != null) {
                    SetValue(component, attr.Value);
                }
            }
    
            /// <devdoc>
            ///    <para>Gets a value
            ///       indicating whether the value of this property needs to be persisted.</para>
            /// </devdoc>
            public override bool ShouldSerializeValue(object component) {
                return false;
            }
        }
        
        /// <devdoc>
        ///    <para>Represents a collection of values.</para>
        /// </devdoc>
        public class StandardValuesCollection : ICollection {
            private ICollection values;
            private Array       valueArray;
            
            /// <devdoc>
            ///    <para>
            ///       Initializes a new instance of the <see cref='System.ComponentModel.TypeConverter.StandardValuesCollection'/>
            ///       class.
            ///    </para>
            /// </devdoc>
            public StandardValuesCollection(ICollection values) {
                if (values == null) {
                    values = new object[0];
                }
                
                Array a = values as Array;
                if (a != null) {
                    valueArray = a;
                }
                
                this.values = values;
            }
            
            /// <devdoc>
            ///    <para>
            ///       Gets the number of objects in the collection.
            ///    </para>
            /// </devdoc>
            public int Count {
                get {
                    if (valueArray != null) {
                        return valueArray.Length;
                    }
                    else {
                        return values.Count;
                    }
                }
            }
            
            /// <devdoc>
            ///    <para>Gets the object at the specified index number.</para>
            /// </devdoc>
            public object this[int index] {
                get {
                    if (valueArray != null) {
                        return valueArray.GetValue(index);
                    }
                    IList list = values as IList;
                    if (list != null) {
                        return list[index];
                    }
                    // No other choice but to enumerate the collection.
                    //
                    valueArray = new object[values.Count];
                    values.CopyTo(valueArray, 0);
                    return valueArray.GetValue(index);
                }
            }

            /// <devdoc>
            ///    <para>Copies the contents of this collection to an array.</para>
            /// </devdoc>
            public void CopyTo(Array array, int index) {
                values.CopyTo(array, index);
            }

            /// <devdoc>
            ///    <para>
            ///       Gets an enumerator for this collection.
            ///    </para>
            /// </devdoc>
            public IEnumerator GetEnumerator() {
                return values.GetEnumerator();
            }
            
            /// <internalonly/>
            /// <devdoc>
            /// Retrieves the count of objects in the collection.
            /// </devdoc>
            int ICollection.Count {
                get {
                    return Count;
                }
            }

            /// <internalonly/>
            /// <devdoc>
            /// Determines if this collection is synchronized.
            /// The ValidatorCollection is not synchronized for
            /// speed.  Also, since it is read-only, there is
            /// no need to synchronize it.
            /// </devdoc>
            bool ICollection.IsSynchronized {
                get {
                    return false;
                }
            }

            /// <internalonly/>
            /// <devdoc>
            /// Retrieves the synchronization root for this
            /// collection.  Because we are not synchronized,
            /// this returns null.
            /// </devdoc>
            object ICollection.SyncRoot {
                get {
                    return null;
                }
            }

            /// <internalonly/>
            /// <devdoc>
            /// Copies the contents of this collection to an array.
            /// </devdoc>
            void ICollection.CopyTo(Array array, int index) {
                CopyTo(array, index);
            }

            /// <internalonly/>
            /// <devdoc>
            /// Retrieves a new enumerator that can be used to
            /// iterate over the values in this collection.
            /// </devdoc>
            IEnumerator IEnumerable.GetEnumerator() {
                return GetEnumerator();
            }
        }
    }
}

