#region License

/*
 * Copyright © 2002-2005 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 System.Globalization;
using Spring.Core;

#endregion

namespace Spring.Objects.Factory.Config
{
	/// <summary>
	/// <see cref="Spring.Objects.Factory.Config.IObjectFactoryPostProcessor"/>
	/// implementation that allows for convenient registration of custom
	/// <see cref="System.ComponentModel.TypeConverter"/>s.
	/// </summary>
	/// <remarks>
	/// <note>
	/// The use of this class is <i>typically</i> not required; the .NET
	/// mechanism of associating a
	/// <see cref="System.ComponentModel.TypeConverter"/> with a
	/// <see cref="System.Type"/>  via the use of the
	/// <see cref="System.ComponentModel.TypeConverterAttribute"/> is the
	/// recommended (and standard) way. This class primarily exists to cover
	/// those cases where third party classes to which one does not have the
	/// source need to be exposed to the type conversion mechanism.
	/// </note>
	/// <p>
	/// Because the
	/// <see cref="Spring.Objects.Factory.Config.CustomConverterConfigurer"/>
	/// class implements the
	/// <see cref="Spring.Objects.Factory.Config.IObjectFactoryPostProcessor"/>
	/// interface, instances of this class that have been exposed in the
	/// scope of an
	/// <see cref="Spring.Context.IApplicationContext"/> will
	/// <i>automatically</i> be picked up by the application context and made
	/// available to the IoC container whenever type conversion is required. If
	/// one is using a
	/// <see cref="Spring.Objects.Factory.Config.CustomConverterConfigurer"/>
	/// object definition within the scope of an
	/// <see cref="Spring.Objects.Factory.IObjectFactory"/>, no such automatic
	/// pickup of the
	/// <see cref="Spring.Objects.Factory.Config.CustomConverterConfigurer"/>
	/// is performed (custom converters will have to be added manually using the
	/// <see cref="Spring.Objects.Factory.Config.IConfigurableObjectFactory.RegisterCustomConverter(Type, TypeConverter)"/>
	/// method). For <i>most</i> application scenarios, one will get better
	/// mileage using the <see cref="Spring.Context.IApplicationContext"/>
	/// abstraction.
	/// </p>
	/// </remarks>
	/// <example>
	/// <p>
	/// The following examples all assume XML based configuration, and use
	/// inner object definitions to define the custom
	/// <see cref="System.ComponentModel.TypeConverter"/> objects (nominally to
	/// avoid polluting the object name space, but also because the
	/// configuration simply reads better that way).
	/// </p>
	/// <code escaped="true">
	/// <object id="customConverterConfigurer"
	///	  type="Spring.Objects.Factory.Config.CustomConverterConfigurer, Spring.Core">
	///   <property name="CustomConverters">
	///     <dictionary>
	///       <entry key="System.Date">
	///         <object type="MyNamespace.MyCustomDateEditor"/>
	///       </entry>
	///       <entry key="MyNamespace.MyObject, MyAssembly">
	///         <object id="myConverter"
	///           type="MyNamespace.MObjectConverter, MyOtherAssembly">
	///           <property name="aProperty" value="..."/>
	///         </object>
	///       </entry>
	///     </dictionary>
	///   </property>
	/// </object>
	/// </code>
	/// <p>
	/// The following example illustrates a complete (albeit naieve) use case
	/// for this class, including a custom
	/// <see cref="System.ComponentModel.TypeConverter"/> implementation, said
	/// converters domain class, and the XML configuration that hooks the
	/// converter in place and makes it available to a Spring.NET container for
	/// use during object resolution.
	/// </p>
	/// <p>
	/// The domain class is a simple data-only object that contains the data
	/// required to send an email message (such as the host and user account
	/// name). A developer would prefer to use a string of the form
	/// <c>UserName=administrator,Password=r1l0k1l3y,Host=localhost</c> to
	/// configure the mail settings and just let the container take care of the
	/// conversion.
	/// </p>
	/// <code language="C#">
	/// namespace ExampleNamespace
	/// {
	///		public sealed class MailSettings
	///		{
	///			private string _userName;
	///			private string _password;
	///			private string _host;
	/// 
	///			public string Host
	///			{
	///				get { return _host; }
	///				set { _host = value; }
	///			}
	/// 
	///		 	public string UserName
	///			{
	///				get { return _userName; }
	///				set { _userName = value; }
	///			}
	/// 
	///			public string Password
	///			{
	///				get { return _password; }
	///				set { _password = value; }
	///			}
	///		}
	/// 
	///		public sealed class MailSettingsConverter : TypeConverter
	///		{
	///			public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
	///		 	{
	/// 			if (typeof (string) == sourceType)
	/// 			{
	/// 				return true;
	/// 			}
	/// 			return base.CanConvertFrom(context, sourceType);
	///			}
	/// 
	/// 		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
	/// 		{
	/// 			string text = value as string;
	/// 			if(text != null) 
	/// 			{
	/// 				MailSettings mailSettings = new MailSettings();
	/// 				string[] tokens = text.Split(',');
	/// 				for (int i = 0; i &lt; tokens.Length; ++i)
	/// 				{
	/// 					string token = tokens[i];
	/// 					string[] settings = token.Split('=');
	/// 					typeof(MailSettings).GetProperty(settings[0])
	/// 						.SetValue(mailSettings, settings[1], null);
	/// 				}
	/// 				return mailSettings;
	///				}
	///				return base.ConvertFrom(context, culture, value);
	///			}
	///		}
	///		
	///		// a very naieve class that uses the MailSettings class...
	///		public sealed class ExceptionLogger
	///		{
	///			private MailSettings _mailSettings;
	///			
	///			public MailSettings MailSettings {
	///			{
	///				set { _mailSettings = value; }
	///			}
	///			
	///			public void Log(object value)
	///			{
	///				Exception ex = value as Exception;
	///				if(ex != null) 
	///				{
	///					// use _mailSettings instance...
	///				}
	///			}
	///		}
	///	}
	/// </code>
	/// <p>
	/// The attendant XML configuration for the above classes would be...
	/// </p>
	/// <code escaped="true">
	/// <object id="emailingExceptionLogger" type="ExampleNamespace.ExceptionLogger, MyAssembly">
	/// 	<property name="MailSettings" value="UserName=administrator,Password=r1l0k1l3y,Host=localhost" />
	/// </object>
	/// <object id="customConverterConfigurer" type="Spring.Objects.Factory.Config.CustomConverterConfigurer, Spring.Core">
	/// 	<property name="CustomConverters">
	/// 		<dictionary>
	/// 			<entry key="ExampleNamespace.MailSettings, MyAssembly">
	/// 				<object type="ExampleNamespace.MailSettingsConverter, MyAssembly" />
	/// 			</entry>
	/// 		</dictionary>
	/// 	</property>
	/// </object>
	/// </code>
	/// </example>
	/// <author>Juergen Hoeller</author>
	/// <author>Simon White (.NET)</author>
	/// <seealso cref="Spring.Objects.Factory.Config.IObjectFactoryPostProcessor"/>
	/// <seealso cref="Spring.Context.IApplicationContext"/>
	/// <seealso cref="Spring.Objects.Factory.Config.IConfigurableObjectFactory.RegisterCustomConverter(Type, TypeConverter)"/>
    [Serializable]
    public class CustomConverterConfigurer : AbstractConfigurer
	{
		private IDictionary _customConverters;

		/// <summary>
		/// The custom converters to register.
		/// </summary>
		/// <remarks>
		/// <p>
		/// The <see cref="System.Collections.IDictionary"/> uses the type name
		/// of the class that requires conversion as the key, and an
		/// <b>instance</b> of the
		/// <see cref="System.ComponentModel.TypeConverter"/> that will effect
		/// the conversion. Alternatively, the actual
		/// <see cref="System.Type"/> of the class that requires conversion
		/// can be used as the key.
		/// </p>
		/// </remarks>
		/// <example>
		/// <p>
		/// <code language="C#">
		/// IDictionary converters = new Hashtable();
		/// converters.Add( "System.Date", new MyCustomDateConverter() );
		/// // a System.Type instance can also be used as the key...
		/// converters.Add( typeof(Color), new MyCustomRBGColorConverter() );
		/// </code>
		/// </p>
		/// </example>
		public IDictionary CustomConverters
		{
			set { this._customConverters = value; }
		}

		/// <summary>
		/// Registers any custom converters with the supplied
		/// <paramref name="factory"/>.
		/// </summary>
		/// <param name="factory">
		/// The object factory to register the converters with.
		/// </param>
		/// <exception cref="Spring.Objects.ObjectsException">
		/// In case of errors.
		/// </exception>
		public override void PostProcessObjectFactory(
			IConfigurableListableObjectFactory factory)
		{
			if (_customConverters != null)
			{
				foreach (DictionaryEntry entry in _customConverters)
				{
					Type requiredType = ResolveRequiredType(entry.Key, "key", "custom type converter");
					TypeConverter converter = ResolveConverter(entry.Value);
					factory.RegisterCustomConverter(requiredType, converter);
				}
			}
		}

		/// <summary>
		/// Resolves the supplied <paramref name="value"/> into a
		/// <see cref="System.ComponentModel.TypeConverter"/> instance.
		/// </summary>
		/// <param name="value">
		/// The object that is to be resolved into a
		/// <see cref="System.ComponentModel.TypeConverter"/> instance.
		/// </param>
		/// <returns>
		/// A resolved <see cref="System.ComponentModel.TypeConverter"/> instance.
		/// </returns>
		/// <exception cref="Spring.Objects.ObjectsException">
		/// If the supplied <paramref name="value"/> is <see langword="null"/>,
		/// or the supplied <paramref name="value"/> cannot be resolved.
		/// </exception>
		protected virtual TypeConverter ResolveConverter(object value)
		{
			TypeConverter converter = value as TypeConverter;
			if (converter == null)
			{
				throw new ObjectInitializationException(
					"Mapped value for custom converter is not a " +
						"[System.ComponentModel.TypeConverter] instance.");
			}
			return converter;
		}

	}
}