#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.Globalization;
using System.Reflection;
using System.Text;

using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.Util;

#endregion

namespace Spring.Objects.Factory.Config
{
    /// <summary>
    /// <see cref="Spring.Objects.Factory.IFactoryObject"/> implementation that
    /// retrieves a static or non-static <b>public</b> field value.
    /// </summary>
    /// <remarks>
    /// <p>
    /// Typically used for retrieving <b>public</b> constants.
    /// </p>
    /// </remarks>
    /// <example>
    /// <p>
    /// The following example retrieves the <see cref="System.DBNull.Value"/> field value...
    /// </p>
    /// <code escaped="true">
    /// <object id="dbnull" type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core">
    ///     <property name="TargetType" value="System.DBNull"/>
    ///     <property name="TargetField" value="Value"/>
    /// </object>
    /// </code>
    /// <p>
    /// The previous example could also have been written using the convenience
    /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.StaticField"/>
    /// property, like so...
    /// </p>
    /// <code escaped="true">
    /// <object id="dbnull" type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core">
    ///     <property name="StaticField" value="System.DBNull.Value"/>
    /// </object>
    /// </code>
    /// <p>
    /// This class also implements the <see cref="Spring.Objects.Factory.IObjectNameAware"/>
    /// interface
    /// (<see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.ObjectName"/>).
    /// If the id (or name) of one's
    /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject"/>
    /// object definition is set to the <see cref="System.Type.AssemblyQualifiedName"/>
    /// of the <see langword="static"/> field to be retrieved, then the id (or
    /// name) of one's object definition will be used for the name of the
    /// <see langword="static"/> field lookup. See below for an example of this
    /// concise style of definition.
    /// </p>
    /// <code escaped="true">
    /// <!-- returns the value of the DBNull.Value field -->
    /// <object id="System.DBNull.Value"
    ///         type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core"/>
    ///
    /// <!-- returns the value of the Type.Delimiter field -->
    /// <object id="System.Type.Delimiter"
    ///         type="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject, Spring.Core"/>
    /// </code>
    /// <p>
    /// The usage for retrieving instance fields is similar. No example is shown
    /// because public instance fields are <i>generally</i> bad practice; but if
    /// you have some legacy code that exposes public instance fields, or if you
    /// just really like coding public instance fields, then you can use this
    /// <see cref="Spring.Objects.Factory.IFactoryObject"/> implementation to
    /// retrieve such field values.
    /// </p>
    /// </example>
    /// <author>Juergen Hoeller</author>
    /// <author>Rick Evans (.NET)</author>
    [Serializable]
    public class FieldRetrievingFactoryObject : IFactoryObject, IInitializingObject, IObjectNameAware
    {
        private string targetField;
        private object targetObject;
        private Type targetType;
        private FieldInfo field;
        private string objectName;
        private string staticField;

        /// <summary>
        /// The <see cref="System.Type.AssemblyQualifiedName"/> of the
        /// <see langword="static"/> field to be retrieved.
        /// </summary>
        public string StaticField
        {
            set { this.staticField = value; }
        }

        /// <summary>
        /// Set the name of the object in the object factory that created this object.
        /// </summary>
        /// <value>
        /// The name of the object in the factory.
        /// </value>
        /// <remarks>
        /// <p>
        /// In the context of the
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject"/>
        /// class, the
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.ObjectName"/>
        /// value will be interepreted as the value of the
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.StaticField"/>
        /// property if no value has been explicitly assigned to the
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.StaticField"/>
        /// property. This allows for concise object definitions with just an id or name;
        /// see the class documentation for
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject"/>
        /// for an example of this style of usage.
        /// </p>
        /// </remarks>
        public string ObjectName
        {
            set { this.objectName = value; }
        }

        /// <summary>
        /// The name of the field the value of which is to be retrieved.
        /// </summary>
        /// <remarks>
        /// <p>
        /// If the
        /// <see cref="Spring.Objects.Factory.Config.FieldRetrievingFactoryObject.TargetObject"/>
        /// has been set (and is not <cref lang="null"/>), then the value of this property
        /// refers to an instance field name; it otherwise refers to a <see langword="static"/>
        /// field name.
        /// </p>
        /// </remarks>
        public string TargetField
        {
            get { return targetField; }
            set { targetField = value; }
        }

        /// <summary>
        /// The object instance on which the field is defined.
        /// </summary>
        public object TargetObject
        {
            get { return targetObject; }
            set { targetObject = value; }
        }

        /// <summary>
        /// The <see cref="System.Type"/> on which the field is defined.
        /// </summary>
        public Type TargetType
        {
            get { return targetType; }
            set { targetType = value; }
        }

        /// <summary>
        /// The <see cref="System.Type"/> of object that this
        /// <see cref="Spring.Objects.Factory.IFactoryObject"/> creates, or
        /// <cref lang="null"/> if not known in advance.
        /// </summary>
        public Type ObjectType
        {
            get { return (this.field == null) ? null : this.field.FieldType; }
        }

        /// <summary>
        /// Is the object managed by this factory a singleton or a prototype?
        /// </summary>
        public bool IsSingleton
        {
            get { return true; }
        }

        /// <summary>
        /// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
        /// after it has set all object properties supplied
        /// (and satisfied <see cref="Spring.Objects.Factory.IObjectFactoryAware"/>
        /// and ApplicationContextAware).
        /// </summary>
        /// <remarks>
        /// <p>
        /// This method allows the object instance to perform initialization only
        /// possible when all object properties have been set and to throw an
        /// exception in the event of misconfiguration.
        /// </p>
        /// </remarks>
        /// <exception cref="System.Exception">
        /// In the event of misconfiguration (such as failure to set an essential
        /// property) or if initialization fails.
        /// </exception>
        public void AfterPropertiesSet()
        {
            if (TargetType != null && TargetObject != null)
            {
                throw new ArgumentException(
                    "Only one of the TargetType or TargetObject properties can be set, not both.");
            }
            if (TargetType == null && TargetObject == null)
            {
                if (TargetField != null)
                {
                    throw new ArgumentException(
                        "Specify the TargetType or TargetObject property in combination with the TargetField property.");
                }

                // if no other property specified, use the object name for the static field expression...
                if (StringUtils.IsNullOrEmpty(this.staticField))
                {
                    this.staticField = this.objectName;
                }
                ParseAndSetFromStaticFieldValue();
            }
            if (TargetField == null)
            {
                throw new ArgumentException("The TargetField property is required.");
            }
            BindingFlags fieldFlags = BindingFlags.Public | BindingFlags.IgnoreCase;
            if (TargetObject == null)
            {
                // a static field...
                fieldFlags |= BindingFlags.Static;
            }
            else
            {
                // an instance field...
                fieldFlags |= BindingFlags.Instance;
                TargetType = TargetObject.GetType();
            }
            this.field = targetType.GetField(TargetField, fieldFlags);
            if (this.field == null)
            {
                throw new ObjectDefinitionStoreException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "No such field '{0}' on [{1}].", TargetField,
                        TargetObject == null ? TargetType : TargetObject));
            }
        }

        /// <summary>
        /// Return an instance (possibly shared or independent) of the object
        /// managed by this factory.
        /// </summary>
        /// <returns>
        /// An instance (possibly shared or independent) of the object managed by
        /// this factory.
        /// </returns>
        /// <see cref="Spring.Objects.Factory.IFactoryObject.GetObject"/>
        public object GetObject()
        {
            if (TargetObject != null)
            {
                return this.field.GetValue(TargetObject);
            }
            else
            {
                return this.field.GetValue(null);
            }
        }

        private void ParseAndSetFromStaticFieldValue()
        {
            TypeAssemblyHolder info = new TypeAssemblyHolder(this.staticField);
            // the info.TypeName should now contains the Type name, followed by a
            // period, followed by the name of the field
            int lastDotIndex = info.TypeName.LastIndexOf('.');
            if (lastDotIndex == -1
                || lastDotIndex == info.TypeName.Length)
            {
                throw new ArgumentException(
                    "The value passed to the StaticField property must be a fully " +
                    "qualified Type plus field name: " +
                    "e.g. 'Example.MyExampleClass.MyField, MyAssembly'");
            }
            string typeName = info.TypeName.Substring(0, lastDotIndex);
            string fieldName = info.TypeName.Substring(lastDotIndex + 1);
            StringBuilder buffer = new StringBuilder(typeName);
            if (info.IsAssemblyQualified)
            {
                buffer.Append(TypeAssemblyHolder.TypeAssemblySeparator);
                buffer.Append(info.AssemblyName);
            }
            TargetType = TypeResolutionUtils.ResolveType(buffer.ToString());
            TargetField = fieldName;
        }
    }
}