/*
 * Copyright © 2002-2011 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.
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Spring.Core.TypeResolution;
using Spring.Objects.Factory.Config;
using Spring.Util;
using Spring.Collections.Generic;

namespace Spring.Objects.Factory.Support
{
    /// <summary>
    /// Common base class for object definitions, factoring out common
    /// functionality from
    /// <see cref="Spring.Objects.Factory.Support.RootObjectDefinition"/> and
    /// <see cref="Spring.Objects.Factory.Support.ChildObjectDefinition"/>.
    /// </summary>
    /// <author>Rod Johnson</author>
    /// <author>Juergen Hoeller</author>
    /// <author>Rick Evans (.NET)</author>
    [Serializable]
    public abstract class AbstractObjectDefinition : ObjectMetadataAttributeAccessor, IConfigurableObjectDefinition, ISerializable
    {
        private const string ScopeSingleton = "singleton";
        private const string ScopePrototype = "prototype";

        private ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
        private MutablePropertyValues propertyValues = new MutablePropertyValues();
        private EventValues eventHandlerValues = new EventValues();
        private MethodOverrides methodOverrides = new MethodOverrides();
        private string resourceDescription;
        private bool isSingleton = true;
        private bool isPrototype;
        private bool isLazyInit;
        private bool isAbstract;
        private string scope = ScopeSingleton;
        private ObjectRole role = ObjectRole.ROLE_APPLICATION;
        
        private string objectTypeName;
        private Type objectType;
        
        private AutoWiringMode autowireMode = AutoWiringMode.No;
        private DependencyCheckingMode dependencyCheck = DependencyCheckingMode.None;
        internal List<string> dependsOn;
        private bool autowireCandidate = true;
        private bool primary;
        private Dictionary<string, AutowireCandidateQualifier> qualifiers;
        private string initMethodName;
        private string destroyMethodName;
        private string factoryMethodName;
        private string factoryObjectName;

        /// <summary>
        /// Creates a new instance of the
        /// <see cref="Spring.Objects.Factory.Support.AbstractObjectDefinition"/>
        /// class.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This is an <see langword="abstract"/> class, and as such exposes no
        /// public constructors.
        /// </p>
        /// </remarks>
        protected AbstractObjectDefinition() : this(null, null)
        {
        }

        /// <summary>
        /// Creates a new instance of the
        /// <see cref="Spring.Objects.Factory.Support.AbstractObjectDefinition"/>
        /// class.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This is an <see langword="abstract"/> class, and as such exposes no
        /// public constructors.
        /// </p>
        /// </remarks>
        protected AbstractObjectDefinition(ConstructorArgumentValues arguments, MutablePropertyValues properties)
        {
            constructorArgumentValues = arguments ?? constructorArgumentValues ?? new ConstructorArgumentValues();
            propertyValues = properties ?? propertyValues ?? new MutablePropertyValues();
        }

        /// <summary>
        /// Creates a new instance of the
        /// <see cref="Spring.Objects.Factory.Support.AbstractObjectDefinition"/>
        /// class.
        /// </summary>
        /// <param name="other">
        /// The object definition used to initialise the member fields of this
        /// instance.
        /// </param>
        /// <remarks>
        /// <p>
        /// This is an <see langword="abstract"/> class, and as such exposes no
        /// public constructors.
        /// </p>
        /// </remarks>
        protected AbstractObjectDefinition(IObjectDefinition other)
        {
            AssertUtils.ArgumentNotNull(other, "other");
            OverrideFrom(other);

            AbstractObjectDefinition aod = other as AbstractObjectDefinition;
            if (aod != null)
            {
                if (aod.HasObjectType)
                {
                    ObjectType = other.ObjectType;
                }
                else
                {
                    ObjectTypeName = other.ObjectTypeName;
                }
                MethodOverrides = new MethodOverrides(aod.MethodOverrides);
                DependencyCheck = aod.DependencyCheck;
            }
            ParentName = other.ParentName;
            IsAbstract = other.IsAbstract;
//            IsSingleton = other.IsSingleton;
            Scope = other.Scope;
            Role = other.Role;
            IsLazyInit = other.IsLazyInit;
            ConstructorArgumentValues
                = new ConstructorArgumentValues(other.ConstructorArgumentValues);
            PropertyValues = new MutablePropertyValues(other.PropertyValues);
            EventHandlerValues = new EventValues(other.EventHandlerValues);

            InitMethodName = other.InitMethodName;
            DestroyMethodName = other.DestroyMethodName;
            IsAutowireCandidate = other.IsAutowireCandidate;
            IsPrimary = other.IsPrimary;
            CopyQualifiersFrom(aod);
            if (other.DependsOn.Count > 0)
            {
                DependsOn = other.DependsOn;
            }
            FactoryMethodName = other.FactoryMethodName;
            FactoryObjectName = other.FactoryObjectName;
            AutowireMode = other.AutowireMode;
            ResourceDescription = other.ResourceDescription;
        }

        /// <summary>
        /// The name of the parent definition of this object definition, if any.
        /// </summary>
        public abstract string ParentName { get; set; }

        /// <summary>
        /// The property values that are to be applied to the object
        /// upon creation.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Setting the value of this property to <see langword="null"/>
        /// will merely result in a new (and empty)
        /// <see cref="Spring.Objects.MutablePropertyValues"/>
        /// collection being assigned to the property value.
        /// </p>
        /// </remarks>
        /// <value>
        /// The property values (if any) for this object; may be an
        /// empty collection but is guaranteed not to be
        /// <see langword="null"/>.
        /// </value>
        public MutablePropertyValues PropertyValues
        {
            get => propertyValues;
            set => propertyValues = value ?? new MutablePropertyValues();
        }

        /// <summary>
        /// Does this definition have any
        /// <see cref="Spring.Objects.Factory.Support.MethodOverrides"/>?
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this definition has at least one
        /// <see cref="Spring.Objects.Factory.Support.MethodOverride"/>.
        /// </value>
        public bool HasMethodOverrides => !MethodOverrides.IsEmpty;

        /// <summary>
        /// The constructor argument values for this object.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Setting the value of this property to <see langword="null"/>
        /// will merely result in a new (and empty)
        /// <see cref="Spring.Objects.Factory.Config.ConstructorArgumentValues"/>
        /// collection being assigned.
        /// </p>
        /// </remarks>
        /// <value>
        /// The constructor argument values (if any) for this object; may be an
        /// empty collection but is guaranteed not to be
        /// <see langword="null"/>.
        /// </value>
        public ConstructorArgumentValues ConstructorArgumentValues
        {
            get => constructorArgumentValues;
            set => constructorArgumentValues = value ?? new ConstructorArgumentValues();
        }

        /// <summary>
        /// The event handler values for this object.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Setting the value of this property to <see langword="null"/>
        /// will merely result in a new (and empty)
        /// <see cref="Spring.Objects.Factory.Config.EventValues"/>
        /// collection being assigned.
        /// </p>
        /// </remarks>
        /// <value>
        /// The event handler values (if any) for this object; may be an
        /// empty collection but is guaranteed not to be
        /// <see langword="null"/>.
        /// </value>
        public EventValues EventHandlerValues
        {
            get => eventHandlerValues;
            set => eventHandlerValues = value ?? new EventValues();
        }

        /// <summary>
        /// The method overrides (if any) for this object.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Setting the value of this property to <see langword="null"/>
        /// will merely result in a new (and empty)
        /// <see cref="Spring.Objects.Factory.Support.MethodOverrides"/>
        /// collection being assigned to the property value.
        /// </p>
        /// </remarks>
        /// <value>
        /// The method overrides (if any) for this object; may be an
        /// empty collection but is guaranteed not to be
        /// <see langword="null"/>.
        /// </value>
        public MethodOverrides MethodOverrides
        {
            get => methodOverrides;
            set => methodOverrides = value ?? new MethodOverrides();
        }

        /// <summary>
        /// The name of the target scope for the object.
        /// Defaults to "singleton", ootb alternative is "prototype". Extended object factories
        /// might support further scopes.
        /// </summary>
        public virtual string Scope
        {
            get => scope;
            set
            {
                AssertUtils.ArgumentNotNull(value, "Scope");
                scope = value;
                isPrototype = 0 == string.Compare(ScopePrototype, value, StringComparison.OrdinalIgnoreCase);
                isSingleton = !isPrototype; // 0 == string.Compare(SCOPE_SINGLETON, value, true);
            }
        }

        /// <summary>
        /// Get or set the role hint for this object definition
        /// </summary>
        public virtual ObjectRole Role
        {
            get => role;
            set => role = value;
        }

        /// <summary>
        /// Is this definition a <b>singleton</b>, with
        /// a single, shared instance returned on all calls to an enclosing
        /// container (typically an
        /// <see cref="Spring.Objects.Factory.IObjectFactory"/> or
        /// <see cref="Spring.Context.IApplicationContext"/>).
        /// </summary>
        /// <remarks>
        /// <p>
        /// If <see langword="false"/>, an object factory will apply the
        /// <b>prototype</b> design pattern, with each caller requesting an
        /// instance getting an independent instance. How this is defined
        /// will depend on the object factory implementation. <b>singletons</b>
        /// are the commoner type.
        /// </p>
        /// </remarks>
        /// <seealso cref="Spring.Objects.Factory.IObjectFactory"/>
        public virtual bool IsSingleton
        {
            get => isSingleton;
            set
            {
                scope = (value ? ScopeSingleton : ScopePrototype);
                isSingleton = value;
                isPrototype = !value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is prototype, with an independent instance
        /// returned for each call.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is prototype; otherwise, <c>false</c>.
        /// </value>
        public virtual bool IsPrototype => isPrototype;

        /// <summary>
        /// Is this object lazily initialized?</summary>
        /// <remarks>
        /// <p>
        /// Only applicable to a singleton object.
        /// </p>
        /// <p>
        /// If <see langword="false"/>, it will get instantiated on startup
        /// by object factories that perform eager initialization of
        /// singletons.
        /// </p>
        /// </remarks>
        public bool IsLazyInit
        {
            get => isLazyInit;
            set => isLazyInit = value;
        }

        /// <summary>
        /// Is this object definition a "template", i.e. not meant to be instantiated
        /// itself but rather just serving as an object definition for configuration 
        /// templates used by <see cref="Spring.Objects.Factory.IObjectFactory.ConfigureObject(object, string)"/>.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this object definition is a "template".
        /// </value>
        public bool IsTemplate => isAbstract || (objectType == null && StringUtils.IsNullOrEmpty(factoryObjectName));

        /// <summary>
        /// Is this object definition "abstract", i.e. not meant to be
        /// instantiated itself but rather just serving as a parent for concrete
        /// child object definitions.
        /// </summary>
        /// <value>
        /// <see langword="true"/> if this object definition is "abstract".
        /// </value>
        public bool IsAbstract
        {
            get => isAbstract;
            set => isAbstract = value;
        }

        /// <summary>
        /// The <see cref="System.Type"/> of the object definition (if any).
        /// </summary>
        /// <value>
        /// A resolved object <see cref="System.Type"/>.
        /// </value>
        /// <exception cref="ApplicationException">
        /// If the <see cref="System.Type"/> of the object definition is not a
        /// resolved <see cref="System.Type"/> or <see langword="null"/>.
        /// </exception>
        /// <seealso cref="AbstractObjectDefinition.HasObjectType"/>
        public Type ObjectType
        {
            get
            {
                if (objectType == null)
                {
                    ThrowApplicationException("Object definition does not carry a resolved System.Type");
                    return null;
                }
                return objectType;
            }
            set => objectType = value;
        }

        private static void ThrowApplicationException(string message)
        {
            throw new ApplicationException(message);
        }

        /// <summary>
        /// Is the <see cref="System.Type"/> of the object definition a resolved
        /// <see cref="System.Type"/>?
        /// </summary>
        public bool HasObjectType => objectType != null;

        /// <summary>
        /// Returns the <see cref="System.Type.FullName"/> of the
        /// <see cref="System.Type"/> of the object definition (if any).
        /// </summary>
        public string ObjectTypeName
        {
            get => objectTypeName ?? objectType?.FullName;
            set => objectTypeName = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// A description of the resource that this object definition
        /// came from (for the purpose of showing context in case of errors).
        /// </summary>
        public string ResourceDescription
        {
            get => resourceDescription;
            set => resourceDescription = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// The autowire mode as specified in the object definition.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This determines whether any automagical detection and setting of
        /// object references will happen. The default is
        /// <see cref="Spring.Objects.Factory.Config.AutoWiringMode.No"/>,
        /// which means that no autowiring will be performed.
        /// </p>
        /// </remarks>
        public AutoWiringMode AutowireMode
        {
            get => autowireMode;
            set => autowireMode = value;
        }

        /// <summary>
        /// Gets the resolved autowire mode.
        /// </summary>
        /// <remarks>
        /// <p>
        /// This resolves
        /// <see cref="Spring.Objects.Factory.Config.AutoWiringMode.AutoDetect"/>
        /// to one of 
        /// <see cref="Spring.Objects.Factory.Config.AutoWiringMode.Constructor"/>
        /// or
        /// <see cref="Spring.Objects.Factory.Config.AutoWiringMode.ByType"/>.
        /// </p>
        /// </remarks>
        public AutoWiringMode ResolvedAutowireMode
        {
            get
            {
                if (AutowireMode == AutoWiringMode.AutoDetect)
                {
                    // Work out whether to apply setter autowiring or constructor autowiring.
                    // If it has a no-arg constructor it's deemed to be setter autowiring,
                    // otherwise we'll try constructor autowiring.
                    ConstructorInfo[] constructors =
                        ObjectType.GetConstructors();
                    foreach (ConstructorInfo ctor in constructors)
                    {
                        if (ctor.GetParameters().Length == 0)
                        {
                            return AutoWiringMode.ByType;
                        }
                    }
                    return AutoWiringMode.Constructor;
                }
                else
                {
                    return AutowireMode;
                }
            }
        }

        /// <summary>
        /// The dependency checking mode.
        /// </summary>
        /// <remarks>
        /// <p>
        /// The default is
        /// <see cref="Spring.Objects.Factory.Support.DependencyCheckingMode.None"/>.
        /// </p>
        /// </remarks>
        public DependencyCheckingMode DependencyCheck
        {
            get => dependencyCheck;
            set => dependencyCheck = value;
        }

        /// <summary>
        /// The object names that this object depends on.
        /// </summary>
        /// <remarks>
        /// <p>
        /// The object factory will guarantee that these objects get initialized
        /// before this object definition.
        /// </p>
        /// <note>
        /// Dependencies are normally expressed through object properties
        /// or constructor arguments. This property should just be necessary for
        /// other kinds of dependencies such as statics (*ugh*) or database
        /// preparation on startup.
        /// </note>
        /// </remarks>
        public IReadOnlyList<string> DependsOn
        {
            get => dependsOn ?? StringUtils.EmptyStringsList;
            set => dependsOn = value != null && value.Count > 0 ? new List<string>(value) : null;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance a candidate for getting autowired into some other
        /// object.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is autowire candidate; otherwise, <c>false</c>.
        /// </value>
        public bool IsAutowireCandidate
        {
            get => autowireCandidate;
            set => autowireCandidate = value;
        }


        /// <summary>
        /// Set whether this bean is a primary autowire candidate.
        /// If this value is true for exactly one bean among multiple
        /// matching candidates, it will serve as a tie-breaker.
        /// </summary>
        public bool IsPrimary 
        { 
            get => primary;
            set => primary = value;
        }

        /// <summary>
        /// Register a qualifier to be used for autowire candidate resolution,
        /// keyed by the qualifier's type name.
        /// <see cref="AutowireCandidateQualifier"/>
        /// </summary>
        public void AddQualifier(AutowireCandidateQualifier qualifier)
        {
            qualifiers = qualifiers ?? new Dictionary<string, AutowireCandidateQualifier>();
            qualifiers.Add(qualifier.TypeName, qualifier);
        }

        /// <summary>
        /// Return whether this bean has the specified qualifier.
        /// </summary>
        public bool HasQualifier(string typeName)
        {
            return qualifiers != null && qualifiers.ContainsKey(typeName);
        }

        /// <summary>
        /// Return the qualifier mapped to the provided type name.
        /// </summary>
        public AutowireCandidateQualifier GetQualifier(string typeName)
        {
            if (qualifiers != null && qualifiers.TryGetValue(typeName, out var qualifier))
            {
                return qualifier;
            }

            return null;
        }

        /// <summary>
        /// Return all registered qualifiers.
        /// </summary>
        /// <returns>the Set of <see cref="AutowireCandidateQualifier"/> objects.</returns>
        public Set<AutowireCandidateQualifier> GetQualifiers()
        {
            return qualifiers != null
                ? new OrderedSet<AutowireCandidateQualifier>(qualifiers.Values)
                : new OrderedSet<AutowireCandidateQualifier>();
        }

        /// <summary>
        /// Copy the qualifiers from the supplied AbstractBeanDefinition to this bean definition.
        /// </summary>
        /// <param name="source">the AbstractBeanDefinition to copy from</param>
        public void CopyQualifiersFrom(AbstractObjectDefinition source)
        {
            Trace.Assert(source != null, "Source must not be null");
            if (source.qualifiers != null && source.qualifiers.Count > 0)
            {
                qualifiers = qualifiers ?? new Dictionary<string, AutowireCandidateQualifier>();
                foreach (var qualifier in source.qualifiers)
                {
                    if (!qualifiers.ContainsKey(qualifier.Key))
                    {
                        qualifiers.Add(qualifier.Key, qualifier.Value);
                    }
                }
            }
        }

        /// <summary>
        /// The name of the initializer method.
        /// </summary>
        /// <remarks>
        /// <p>
        /// The default value is the <see cref="String.Empty"/> constant,
        /// in which case there is no initializer method.
        /// </p>
        /// </remarks>
        public string InitMethodName
        {
            get => initMethodName;
            set => initMethodName = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// Return the name of the destroy method.
        /// </summary>
        /// <remarks>
        /// <p>
        /// The default value is the <see cref="String.Empty"/> constant,
        /// in which case there is no destroy method.
        /// </p>
        /// </remarks>
        public string DestroyMethodName
        {
            get => destroyMethodName;
            set => destroyMethodName = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// The name of the factory method to use (if any).
        /// </summary>
        /// <remarks>
        /// <p>
        /// This method will be invoked with constructor arguments, or with no
        /// arguments if none are specified. The <see langword="static"/>
        /// method will be invoked on the specified
        /// <see cref="Spring.Objects.Factory.Config.IObjectDefinition.ObjectType"/>.
        /// </p>
        /// </remarks>
        public string FactoryMethodName
        {
            get => factoryMethodName;
            set => factoryMethodName = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// The name of the factory object to use (if any).
        /// </summary>
        public string FactoryObjectName
        {
            get => factoryObjectName;
            set => factoryObjectName = StringUtils.GetTextOrNull(value);
        }

        /// <summary>
        /// Does this object definition have any constructor argument values?
        /// </summary>
        /// <value>
        /// <see langword="true"/> if his object definition has at least one
        /// element in it's
        /// <see cref="Spring.Objects.Factory.Support.AbstractObjectDefinition.ConstructorArgumentValues"/>
        /// property.
        /// </value>
        public bool HasConstructorArgumentValues => constructorArgumentValues != null
                                                            && !constructorArgumentValues.Empty;

        /// <summary>
        /// Resolves the type of the object, resolving it from a specified
        /// object type name if necessary. 
        /// </summary>
        /// <returns>
        /// A resolved <see cref="System.Type"/> instance.
        /// </returns>
        /// <exception cref="System.TypeLoadException">
        /// If the type cannot be resolved.
        /// </exception>
        public Type ResolveObjectType()
        {
            string typeName = ObjectTypeName;
            if (typeName == null)
            {
                return null;
            }
            Type resolvedType = TypeResolutionUtils.ResolveType(typeName);
            ObjectType = resolvedType;
            return resolvedType;
        }

        /// <summary>
        /// Validate this object definition.
        /// </summary>
        /// <exception cref="Spring.Objects.Factory.Support.ObjectDefinitionValidationException">
        /// In the case of a validation failure.
        /// </exception>
        public virtual void Validate()
        {
            if (IsLazyInit && !IsSingleton)
            {
                throw new ObjectDefinitionValidationException(
                    "Lazy initialization is only applicable to singleton objects.");
            }
            if (HasMethodOverrides && StringUtils.HasText(FactoryMethodName))
            {
                throw new ObjectDefinitionValidationException(
                    "Cannot combine static factory method with method overrides: " +
                    "the static factory method must create the instance.");
            }
            if (HasObjectType)
            {
                PrepareMethodOverrides();
            }
        }

        /// <summary>
        /// Validates all <see cref="MethodOverrides"/> 
        /// </summary>
        public virtual void PrepareMethodOverrides()
        {
            // ascertain that the various lookup methods exist...
            foreach (MethodOverride mo in MethodOverrides)
            {
                PrepareMethodOverride(mo);
            }
        }

        /// <summary>
        /// Validate the supplied <paramref name="methodOverride"/>.
        /// </summary>
        /// <param name="methodOverride">
        /// The <see cref="Spring.Objects.Factory.Support.MethodOverride"/>
        /// to be validated.
        /// </param>
        protected void PrepareMethodOverride(MethodOverride methodOverride)
        {
            if (!ReflectionUtils.HasAtLeastOneMethodWithName(ObjectType, methodOverride.MethodName))
            {
                throw new ObjectDefinitionValidationException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "Invalid method override: no method with name '{0}' on class [{1}].",
                        methodOverride.MethodName, ObjectTypeName));
            }
            //TODO investigate setting overloaded at this point using MethodCountForName...
            //Test SunnyDayReplaceMethod_WithArgumentAcceptingReplacerWithNoTypeFragmentsSpecified
            // will fail if doing this optimization.
        }

        /// <summary>
        /// Override settings in this object definition from the supplied
        /// <paramref name="other"/> object definition.
        /// </summary>
        /// <param name="other">
        /// The object definition used to override the member fields of this instance.
        /// </param>
        public virtual void OverrideFrom(IObjectDefinition other)
        {
            AssertUtils.ArgumentNotNull(other, "other");

            IsAbstract = other.IsAbstract;
            Scope = other.Scope;
            IsLazyInit = other.IsLazyInit;
            ConstructorArgumentValues.AddAll(other.ConstructorArgumentValues);
            PropertyValues.AddAll(other.PropertyValues.PropertyValues);
            EventHandlerValues.AddAll(other.EventHandlerValues);
            if (StringUtils.HasText(other.ObjectTypeName))
            {
                ObjectTypeName = other.ObjectTypeName;
            }
            if (StringUtils.HasText(other.InitMethodName))
            {
                InitMethodName = other.InitMethodName;
            }
            if (StringUtils.HasText(other.DestroyMethodName))
            {
                DestroyMethodName = other.DestroyMethodName;
            }
            if (StringUtils.HasText(other.FactoryObjectName))
            {
                FactoryObjectName = other.FactoryObjectName;
            }
            if (StringUtils.HasText(other.FactoryMethodName))
            {
                FactoryMethodName = other.FactoryMethodName;
            }
            if (other.DependsOn != null && other.DependsOn.Count > 0)
            {
                var deps = new List<string>(other.DependsOn.Count + (DependsOn?.Count).GetValueOrDefault());
                deps.AddRange(other.DependsOn);
                if (DependsOn != null && DependsOn.Count > 0)
                {
                    deps.AddRange(DependsOn);
                }
                DependsOn = deps;
            }
            AutowireMode = other.AutowireMode;
            ResourceDescription = other.ResourceDescription;
            IsPrimary = other.IsPrimary;
            IsAutowireCandidate = other.IsAutowireCandidate;

            if (other is AbstractObjectDefinition aod)
            {
                if (other.ObjectTypeName != null)
                {
                    ObjectTypeName = other.ObjectTypeName;
                }
                if (aod.HasObjectType)
                {
                    ObjectType = other.ObjectType;
                }

                MethodOverrides.AddAll(aod.MethodOverrides);
                DependencyCheck = aod.DependencyCheck;
                CopyQualifiersFrom(aod);
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents the current
        /// <see cref="System.Object"/>. 
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents the current
        /// <see cref="System.Object"/>. 
        /// </returns>
        public override string ToString()
        {
            StringBuilder buffer = new StringBuilder(string.Format("Class [{0}]", ObjectTypeName));
            buffer.Append("; Abstract = ").Append(IsAbstract);
            buffer.Append("; Parent = ").Append(ParentName);
            buffer.Append("; Scope = ").Append(Scope);
            buffer.Append("; Singleton = ").Append(IsSingleton);
            buffer.Append("; LazyInit = ").Append(IsLazyInit);
            buffer.Append("; Autowire = ").Append(AutowireMode);
            buffer.Append("; Autowire-Candidate = ").Append(IsAutowireCandidate);
            buffer.Append("; Primary = ").Append(IsPrimary);
            buffer.Append("; DependencyCheck = ").Append(DependencyCheck);
            buffer.Append("; InitMethodName = ").Append(InitMethodName);
            buffer.Append("; DestroyMethodName = ").Append(DestroyMethodName);
            buffer.Append("; FactoryMethodName = ").Append(FactoryMethodName);
            buffer.Append("; FactoryObjectName = ").Append(FactoryObjectName);
            if (StringUtils.HasText(ResourceDescription))
            {
                buffer.Append("; defined in = ").Append(ResourceDescription);
            }
            return buffer.ToString();
        }
       
        protected AbstractObjectDefinition(SerializationInfo info, StreamingContext context)
        {
            constructorArgumentValues = (ConstructorArgumentValues) info.GetValue("constructorArgumentValues", typeof(ConstructorArgumentValues));
            propertyValues = (MutablePropertyValues) info.GetValue("propertyValues", typeof(MutablePropertyValues));
            eventHandlerValues= (EventValues) info.GetValue("eventHandlerValues", typeof(EventValues));
            methodOverrides= (MethodOverrides) info.GetValue("methodOverrides", typeof(MethodOverrides));
            resourceDescription = info.GetString("resourceDescription");
            isSingleton = info.GetBoolean("isSingleton");
            isPrototype = info.GetBoolean("isPrototype");
            isLazyInit = info.GetBoolean("isLazyInit");
            isAbstract = info.GetBoolean("isAbstract");
            scope= info.GetString("scope");
            role = (ObjectRole) info.GetValue("role", typeof(ObjectRole));
            
            var objectTypeName = info.GetString("objectTypeName");
            objectType = objectTypeName != null ? Type.GetType(objectTypeName) : null;
            
            autowireMode = (AutoWiringMode) info.GetValue("autowireMode", typeof(AutoWiringMode));
            dependencyCheck= (DependencyCheckingMode) info.GetValue("dependencyCheck", typeof(DependencyCheckingMode));
            dependsOn = (List<string>) info.GetValue("dependsOn", typeof(List<string>));
            autowireCandidate = info.GetBoolean("autowireCandidate");
            primary = info.GetBoolean("primary");
            qualifiers = (Dictionary<string, AutowireCandidateQualifier>) info.GetValue("qualifiers", typeof(Dictionary<string, AutowireCandidateQualifier>));
            initMethodName = info.GetString("initMethodName");
            destroyMethodName = info.GetString("destroyMethodName");
            factoryMethodName = info.GetString("factoryMethodName" );
            factoryObjectName= info.GetString("factoryObjectName");
        }
        
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("constructorArgumentValues", constructorArgumentValues);
            info.AddValue("propertyValues", propertyValues);
            info.AddValue("eventHandlerValues", eventHandlerValues);
            info.AddValue("methodOverrides", methodOverrides);
            info.AddValue("resourceDescription", resourceDescription);
            info.AddValue("isSingleton", isSingleton);
            info.AddValue("isPrototype", isPrototype);
            info.AddValue("isLazyInit", isLazyInit);
            info.AddValue("isAbstract", isAbstract);
            info.AddValue("scope", scope);
            info.AddValue("role", role);
            info.AddValue("objectTypeName", objectType.AssemblyQualifiedName);
            info.AddValue("autowireMode", autowireMode);
            info.AddValue("dependencyCheck", dependencyCheck);
            info.AddValue("dependsOn", dependsOn);
            info.AddValue("autowireCandidate", autowireCandidate);
            info.AddValue("primary", primary);
            info.AddValue("qualifiers", qualifiers);
            info.AddValue("initMethodName", initMethodName);
            info.AddValue("destroyMethodName", destroyMethodName);
            info.AddValue("factoryMethodName", factoryMethodName);
            info.AddValue("factoryObjectName", factoryObjectName);
        }
    }
}