File: System\ComponentModel\Design\Serialization\CodeDomDesignerLoader.cs
Web Access
Project: src\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.Reflection;
using System.Text;
 
namespace System.ComponentModel.Design.Serialization;
 
/// <summary>
///  DesignerLoader. This class is responsible for loading a designer document.
///  Where and how this load occurs is a private matter for the designer loader.
///  The designer loader will be handed to an IDesignerHost instance. This instance,
///  when it is ready to load the document, will call BeginLoad, passing an instance
///  of IDesignerLoaderHost. The designer loader will load up the design surface
///  using the host interface, and call EndLoad on the interface when it is done.
///  The error collection passed into EndLoad should be empty or null to indicate a
///  successful load, or it should contain a collection of exceptions that
///  describe the error.
///
///  Once a document is loaded, the designer loader is also responsible for
///  writing any changes made to the document back whatever storage the
///  loader used when loading the document.
/// </summary>
public abstract partial class CodeDomDesignerLoader : BasicDesignerLoader, INameCreationService, IDesignerSerializationService
{
    private static readonly int s_stateCodeDomDirty = BitVector32.CreateMask();                                 // True if the code dom tree is dirty, meaning it must be integrated back with the code file.
    private static readonly int s_stateCodeParserChecked = BitVector32.CreateMask(s_stateCodeDomDirty);         // True if we have searched for a parser.
    private static readonly int s_stateOwnTypeResolution = BitVector32.CreateMask(s_stateCodeParserChecked);    // True if we have added our own type resolution service
 
    // State for the designer loader.
    private BitVector32 _state;
    private IExtenderProvider[]? _extenderProviders;
    private IExtenderProviderService? _extenderProviderService;
 
    // State for the code dom parser / generator
    private ICodeGenerator? _codeGenerator;
 
    // The following fields are setup by EnsureDocument and deleted by ClearDocument.
    private CodeDomSerializer? _rootSerializer;
    private TypeCodeDomSerializer? _typeSerializer;
    private CodeCompileUnit? _documentCompileUnit;
    private CodeNamespace? _documentNamespace;
    private CodeTypeDeclaration? _documentType;
 
    /// <summary>
    ///  This abstract property returns the code dom provider that should
    ///  be used by this designer loader.
    /// </summary>
    protected abstract CodeDomProvider? CodeDomProvider { get; }
 
    /// <summary>
    ///  The TypeResolutionService property returns a type resolution service that the code dom
    ///  serializers will use when resolving types. The CodeDomDesignerLoader automatically
    ///  adds this type resolver as a service to the service container during Initialize if it
    ///  is non-null. While the type resolution service is optional in many scenarios, it is required for
    ///  code interpretation because source code contains type names, but no assembly references.
    /// </summary>
    protected abstract ITypeResolutionService? TypeResolutionService { get; }
 
    /// <summary>
    ///  This is the reverse of EnsureDocument. It clears the document state which will
    ///  cause us to parse the next time we need to access it.
    /// </summary>
    private void ClearDocument()
    {
        if (_documentType is not null)
        {
            LoaderHost.RemoveService<CodeTypeDeclaration>();
            _documentType = null;
            _documentNamespace = null;
            _documentCompileUnit = null;
            _rootSerializer = null;
            _typeSerializer = null;
        }
    }
 
    /// <summary>
    ///  Disposes this designer loader. The designer host will call
    ///  this method when the design document itself is being destroyed.
    ///  Once called, the designer loader will never be called again.
    ///  This implementation flushes any changes and removes any
    ///  previously added services.
    /// </summary>
    public override void Dispose()
    {
        if (TryGetService(out IComponentChangeService? componentChangeService))
        {
            componentChangeService.ComponentRemoved -= OnComponentRemoved;
            componentChangeService.ComponentRename -= OnComponentRename;
        }
 
        if (TryGetService(out IDesignerHost? host))
        {
            host.RemoveService<INameCreationService>();
            host.RemoveService<IDesignerSerializationService>();
            host.RemoveService<ComponentSerializationService>();
 
            if (_state[s_stateOwnTypeResolution])
            {
                host.RemoveService<ITypeResolutionService>();
                _state[s_stateOwnTypeResolution] = false;
            }
        }
 
        if (_extenderProviderService is not null)
        {
            foreach (IExtenderProvider provider in _extenderProviders!)
            {
                _extenderProviderService.RemoveExtenderProvider(provider);
            }
        }
 
        base.Dispose();
    }
 
#if DEBUG
    /// <summary>
    ///  Internal debug method to dump a code dom tree to text.
    /// </summary>
    internal static void DumpTypeDeclaration(CodeTypeDeclaration? typeDeclaration)
    {
        if (typeDeclaration is null)
        {
            return;
        }
 
        ICodeGenerator codeGenerator = new Microsoft.CSharp.CSharpCodeProvider().CreateGenerator();
        using StringWriter sw = new(CultureInfo.InvariantCulture);
 
        try
        {
            codeGenerator.GenerateCodeFromType(typeDeclaration, sw, null!);
        }
        catch (Exception ex)
        {
            sw.WriteLine($"Error during declaration dump: {ex.Message}");
        }
 
        // spit this line by line so it respects the indent.
        StringReader sr = new(sw.ToString());
 
        for (string? line = sr.ReadLine(); line is not null; line = sr.ReadLine())
        {
            Debug.WriteLine(line);
        }
    }
#endif
 
    /// returns true if the given type has a root designer.
    private static bool HasRootDesignerAttribute(Type t)
    {
        AttributeCollection attributes = TypeDescriptor.GetAttributes(t);
 
        for (int i = 0; i < attributes.Count; i++)
        {
            if (attributes[i] is DesignerAttribute designerAttribute)
            {
                Type? attributeBaseType = Type.GetType(designerAttribute.DesignerBaseTypeName);
 
                if (attributeBaseType is not null && attributeBaseType == typeof(IRootDesigner))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    /// <summary>
    ///  This ensures that we can load the code dom elements into a
    ///  document. It ensures that there is a code dom provider, that
    ///  the provider can parse the code, and that the returned
    ///  code compile unit contains a class that can be deserialized
    ///  through a code dom serializer. During all of this checking
    ///  it establishes state in our member variables so that
    ///  calls after it can assume that the variables are set. This
    ///  will throw a human readable exception if any part of the
    ///  process fails.
    /// </summary>
    [MemberNotNull(nameof(_documentCompileUnit))]
    [MemberNotNull(nameof(_documentType))]
    private void EnsureDocument(IDesignerSerializationManager manager)
    {
        Debug.Assert(manager is not null, "Should pass a serialization manager into EnsureDocument");
 
        // If we do not yet have the compile unit, ask for it.
        if (_documentCompileUnit is null)
        {
            Debug.Assert(_documentType is null && _documentNamespace is null, "We have no compile unit but we still have a type or namespace. Our state is inconsistent.");
            _documentCompileUnit = Parse();
 
            if (_documentCompileUnit is null)
            {
                Exception ex = new NotSupportedException(SR.CodeDomDesignerLoaderNoLanguageSupport)
                {
                    HelpLink = SR.CodeDomDesignerLoaderNoLanguageSupport
                };
 
                throw ex;
            }
        }
 
        // Search namespaces and types to identify something that we can load.
        if (_documentType is null)
        {
            // We keep track of any failures here. If we failed to find a type this
            // array list will contain a list of strings listing what we have tried.
            List<string>? failures = null;
            bool firstClass = true;
 
            if (_documentCompileUnit.UserData[typeof(InvalidOperationException)] is InvalidOperationException invalidOperation)
            {
                _documentCompileUnit = null; // not efficient but really a corner case...
 
                throw invalidOperation;
            }
 
            // Look in the compile unit for a class we can load. The first one we find
            // that has an appropriate serializer attribute, we take.
            foreach (CodeNamespace codeNamespace in _documentCompileUnit.Namespaces)
            {
                foreach (CodeTypeDeclaration typeDeclaration in codeNamespace.Types)
                {
                    // Uncover the base type of this class. In case we totally fail
                    // we document each time we were unable to load a particular class.
                    Type? baseType = null;
 
                    foreach (CodeTypeReference typeReference in typeDeclaration.BaseTypes)
                    {
                        Type? t = LoaderHost.GetType(CodeDomSerializerBase.GetTypeNameFromCodeTypeReference(manager, typeReference));
 
                        if (t is null)
                        {
                            failures ??= [];
                            failures.Add(string.Format(SR.CodeDomDesignerLoaderDocumentFailureTypeNotFound, typeDeclaration.Name, typeReference.BaseType));
                        }
                        else if (!t.IsInterface)
                        {
                            baseType = t;
                            break;
                        }
                    }
 
                    // We have a potential type. The next step is to examine the type's attributes
                    // and see if there is a root designer serializer attribute that supports the
                    // code dom.
                    if (baseType is not null)
                    {
                        bool foundAttribute = false;
 
                        // Backwards Compat:  RootDesignerSerializer is obsolete, but we need to still
                        // be compatible and read it.
                        // Walk the member attributes for this type, looking for an appropriate serializer attribute.
                        AttributeCollection attributes = TypeDescriptor.GetAttributes(baseType);
 
                        foreach (Attribute attribute in attributes)
                        {
                            if (attribute is RootDesignerSerializerAttribute serializerAttribute)
                            {
                                // This serializer must support a CodeDomSerializer or we're not interested.
                                if (serializerAttribute.SerializerBaseTypeName is not null && LoaderHost.GetType(serializerAttribute.SerializerBaseTypeName) == typeof(CodeDomSerializer))
                                {
                                    Type? serializerType = LoaderHost.GetType(serializerAttribute.SerializerTypeName!);
 
                                    if (serializerType is not null)
                                    {
                                        foundAttribute = true;
 
                                        if (firstClass)
                                        {
                                            _rootSerializer = (CodeDomSerializer?)Activator.CreateInstance(serializerType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, binder: null, args: null, culture: null);
                                            break;
                                        }
                                        else
                                        {
                                            throw new InvalidOperationException(string.Format(SR.CodeDomDesignerLoaderSerializerTypeNotFirstType, typeDeclaration.Name));
                                        }
                                    }
                                }
                            }
                        }
 
                        // add a check for root designer -- this allows an extra level of checking so we can skip classes
                        // that cannot be designed.
                        if (_rootSerializer is null && HasRootDesignerAttribute(baseType))
                        {
                            _typeSerializer = manager.GetSerializer<TypeCodeDomSerializer>(baseType);
 
                            if (!firstClass && _typeSerializer is not null)
                            {
                                _typeSerializer = null;
                                _documentCompileUnit = null;
 
                                throw new InvalidOperationException(string.Format(SR.CodeDomDesignerLoaderSerializerTypeNotFirstType, typeDeclaration.Name));
                            }
                        }
 
                        // If we didn't find a serializer for this type, report it.
                        if (_rootSerializer is null && _typeSerializer is null)
                        {
                            failures ??= [];
 
                            if (foundAttribute)
                            {
                                failures.Add(string.Format(SR.CodeDomDesignerLoaderDocumentFailureTypeDesignerNotInstalled, typeDeclaration.Name, baseType.FullName));
                            }
                            else
                            {
                                failures.Add(string.Format(SR.CodeDomDesignerLoaderDocumentFailureTypeNotDesignable, typeDeclaration.Name, baseType.FullName));
                            }
                        }
                    }
 
                    // If we found a serializer, then we're done. Save this type and namespace for later use.
                    if (_rootSerializer is not null || _typeSerializer is not null)
                    {
                        _documentNamespace = codeNamespace;
                        _documentType = typeDeclaration;
                        break;
                    }
 
                    firstClass = false;
                }
 
                if (_documentType is not null)
                {
                    break;
                }
            }
 
            // If we did not get a document type, throw, because we're unable to continue.
            if (_documentType is null)
            {
                // The entire compile unit needs to be thrown away so
                // we can later reparse.
                _documentCompileUnit = null;
 
                // Did we get any reasons why we can't load this document?  If so, synthesize a nice
                // description to the user.
                Exception ex;
 
                if (failures is not null)
                {
                    StringBuilder builder = new(Environment.NewLine);
                    builder.AppendJoin(Environment.NewLine, failures);
 
                    ex = new InvalidOperationException(string.Format(SR.CodeDomDesignerLoaderNoRootSerializerWithFailures, builder))
                    {
                        HelpLink = SR.CodeDomDesignerLoaderNoRootSerializer
                    };
                }
                else
                {
                    ex = new InvalidOperationException(SR.CodeDomDesignerLoaderNoRootSerializer)
                    {
                        HelpLink = SR.CodeDomDesignerLoaderNoRootSerializer
                    };
                }
 
                throw ex;
            }
            else
            {
                // We are successful. At this point, we want to provide some of these
                // code dom elements as services for outside parties to use.
                LoaderHost.AddService(_documentType);
            }
        }
    }
 
    /// <summary>
    ///  Takes the given code element and integrates it into the existing CodeDom
    ///  tree stored in _documentCompileUnit. This returns true if any changes
    ///  were made to the tree.
    /// </summary>
    [MemberNotNull(nameof(_documentCompileUnit))]
    [MemberNotNull(nameof(_documentType))]
    private bool IntegrateSerializedTree(IDesignerSerializationManager manager, CodeTypeDeclaration newDecl)
    {
        EnsureDocument(manager);
        CodeTypeDeclaration docDeclaration = _documentType;
        bool caseInsensitive = false;
        bool codeDomDirty = false;
        CodeDomProvider? provider = CodeDomProvider;
 
        if (provider is not null)
        {
            caseInsensitive = ((provider.LanguageOptions & LanguageOptions.CaseInsensitive) != 0);
        }
 
        // Update the class name of the code type, in case it is different.
        if (!string.Equals(docDeclaration.Name, newDecl.Name, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
        {
            docDeclaration.Name = newDecl.Name;
            codeDomDirty = true;
        }
 
        if (!docDeclaration.Attributes.Equals(newDecl.Attributes))
        {
            docDeclaration.Attributes = newDecl.Attributes;
            codeDomDirty = true;
        }
 
        // Now, hash up the member names in the document and use this
        // when determining what to add and what to replace. In addition,
        // we also build up a set of indexes into approximate locations for
        // inserting fields and methods.
        int fieldInsertLocation = 0;
        bool lockField = false;
        int methodInsertLocation = 0;
        bool lockMethod = false;
        Dictionary<string, int> docMemberHash = new(docDeclaration.Members.Count, caseInsensitive
            ? StringComparer.InvariantCultureIgnoreCase
            : StringComparer.InvariantCulture);
        int memberCount = docDeclaration.Members.Count;
 
        for (int i = 0; i < memberCount; i++)
        {
            CodeTypeMember member = docDeclaration.Members[i];
            string memberName = member switch
            {
                CodeConstructor => ".ctor",
                CodeTypeConstructor => ".cctor",
                _ => member.Name,
            };
 
            docMemberHash[memberName] = i;
 
            if (member is CodeMemberField)
            {
                if (!lockField)
                {
                    fieldInsertLocation = i;
                }
            }
            else
            {
                if (fieldInsertLocation > 0)
                {
                    lockField = true;
                }
            }
 
            if (member is CodeMemberMethod)
            {
                if (!lockMethod)
                {
                    methodInsertLocation = i;
                }
            }
            else
            {
                if (methodInsertLocation > 0)
                {
                    lockMethod = true;
                }
            }
        }
 
        // Now start looking through the new declaration and process it.
        // We are index driven, so if we need to add new values we put
        // them into an array list, and post process them.
        List<CodeTypeMember> newElements = [];
 
        foreach (CodeTypeMember member in newDecl.Members)
        {
            string memberName = member is CodeConstructor ? ".ctor" : member.Name;
            if (docMemberHash.TryGetValue(memberName, out int slot))
            {
                CodeTypeMember existingMember = docDeclaration.Members[slot];
 
                if (existingMember == member)
                {
                    continue;
                }
 
                if (member is CodeMemberField newField)
                {
                    if (existingMember is CodeMemberField docField)
                    {
                        // We will be case-sensitive always in working out whether to replace the field
                        if ((string.Equals(newField.Name, docField.Name)) && newField.Attributes == docField.Attributes && TypesEqual(newField.Type, docField.Type))
                        {
                            continue;
                        }
                        else
                        {
                            docDeclaration.Members[slot] = member;
                        }
                    }
                    else
                    {
                        // We adding a field with the same name as a method. This should cause a
                        // compile error, but we don't want to clobber the existing method.
                        newElements.Add(member);
                    }
                }
                else if (member is CodeMemberMethod newMethod)
                {
                    if (existingMember is CodeMemberMethod and not CodeConstructor)
                    {
                        // If there is an existing constructor, preserve it.
                        // For methods, we do not want to replace the method; rather, we
                        // just want to replace its contents. This helps to preserve
                        // the layout of the file.
                        CodeMemberMethod existingMethod = (CodeMemberMethod)existingMember;
 
                        existingMethod.Statements.Clear();
                        existingMethod.Statements.AddRange(newMethod.Statements);
                    }
                }
                else
                {
                    docDeclaration.Members[slot] = member;
                }
 
                codeDomDirty = true;
            }
            else
            {
                newElements.Add(member);
            }
        }
 
        // Now, process all new elements.
        foreach (CodeTypeMember member in newElements)
        {
            if (member is CodeMemberField)
            {
                if (fieldInsertLocation >= docDeclaration.Members.Count)
                {
                    docDeclaration.Members.Add(member);
                }
                else
                {
                    docDeclaration.Members.Insert(fieldInsertLocation, member);
                }
 
                fieldInsertLocation++;
                methodInsertLocation++;
                codeDomDirty = true;
            }
            else if (member is CodeMemberMethod)
            {
                if (methodInsertLocation >= docDeclaration.Members.Count)
                {
                    docDeclaration.Members.Add(member);
                }
                else
                {
                    docDeclaration.Members.Insert(methodInsertLocation, member);
                }
 
                methodInsertLocation++;
                codeDomDirty = true;
            }
            else
            {
                // For rare members, just add them to the end.
                docDeclaration.Members.Add(member);
                codeDomDirty = true;
            }
        }
 
        return codeDomDirty;
    }
 
    /// <summary>
    ///  This method is called immediately after the first time
    ///  BeginLoad is invoked. This is an appropriate place to
    ///  add custom services to the loader host. Remember to
    ///  remove any custom services you add here by overriding
    ///  Dispose.
    /// </summary>
    protected override void Initialize()
    {
        base.Initialize();
 
        ServiceCreatorCallback callback = new(OnCreateService);
 
        LoaderHost.AddService<ComponentSerializationService>(callback);
        LoaderHost.AddService<INameCreationService>(this);
        LoaderHost.AddService<IDesignerSerializationService>(this);
 
        // The code dom designer loader requires a working ITypeResolutionService to
        // function. See if someone added one already, and if not, provide
        // our own.
        if (!TryGetService(out ITypeResolutionService? typeResolutionService))
        {
            typeResolutionService = TypeResolutionService;
 
            if (typeResolutionService is null)
            {
                throw new InvalidOperationException(SR.CodeDomDesignerLoaderNoTypeResolution);
            }
 
            LoaderHost.AddService(typeResolutionService);
            _state[s_stateOwnTypeResolution] = true;
        }
 
        if (TryGetService(out _extenderProviderService))
        {
            _extenderProviders =
            [
                new ModifiersExtenderProvider(),
                new ModifiersInheritedExtenderProvider()
            ];
 
            foreach (IExtenderProvider p in _extenderProviders)
            {
                _extenderProviderService.AddExtenderProvider(p);
            }
        }
    }
 
    /// <summary>
    ///  Determines if the designer needs to be reloaded. It does this
    ///  by examining the code dom tree for changes. This does not check
    ///  for outside influences; the caller should already think a reload
    ///  is needed -- this is just a last optimization.
    /// </summary>
    protected override bool IsReloadNeeded()
    {
        if (!base.IsReloadNeeded())
        {
            return false;
        }
 
        // If we have no document, we definitely need a reload.
        if (_documentType is null)
        {
            return true;
        }
 
        // If we can't get to a code dom provider, or if that provider doesn't
        // implement ICodeDomDesignerReload, we can't optimize the reload, so we
        // just assume it is needed.
        if (CodeDomProvider is not ICodeDomDesignerReload reloader)
        {
            return true;
        }
 
        bool reload = true;
 
        // Parse the file and see if we actually need to reload.
        string oldTypeName = _documentType.Name;
 
        // StartTimingMark();
        try
        {
            ClearDocument();
            EnsureDocument(GetService<IDesignerSerializationManager>()!);
        }
        catch
        {
            // If the document is not in a state where we can get any information
            // from it, we will assume this is a reload. The error will then
            // be displayed to the user when the designer does actually
            // reload.
        }
 
        // EndTimingMark("Reload Parse I");
        if (_documentCompileUnit is not null)
        {
            reload = reloader.ShouldReloadDesigner(_documentCompileUnit);
            reload |= (_documentType is null || !_documentType.Name.Equals(oldTypeName));
        }
 
        return reload;
    }
 
    /// <summary>
    ///  This method should be called by the designer loader service
    ///  when the first dependent load has started. This initializes
    ///  the state of the code dom loader and prepares it for loading.
    ///  By default, the designer loader provides
    ///  IDesignerLoaderService itself, so this is called automatically.
    ///  If you provide your own loader service, or if you choose not
    ///  to provide a loader service, you are responsible for calling
    ///  this method. BeginLoad will automatically call this, either
    ///  indirectly by calling AddLoadDependency if IDesignerLoaderService
    ///  is available, or directly if it is not.
    /// </summary>
    protected override void OnBeginLoad()
    {
        // Make sure that we're removed any event sinks we added after we finished the load.
 
        if (TryGetService(out IComponentChangeService? componentChangeService))
        {
            componentChangeService.ComponentRemoved -= OnComponentRemoved;
            componentChangeService.ComponentRename -= OnComponentRename;
        }
 
        base.OnBeginLoad();
    }
 
    /// <summary>
    ///  This method is called immediately before the document is unloaded.
    ///  The document may be unloaded in preparation for reload, or
    ///  if the document failed the load. If you added document-specific
    ///  services in OnBeginLoad or OnEndLoad, you should remove them
    ///  here.
    /// </summary>
    protected override void OnBeginUnload()
    {
        base.OnBeginUnload();
        ClearDocument();
    }
 
    /// <summary>
    ///  This is called whenever a component is removed from the design surface.
    /// </summary>
    private void OnComponentRemoved(object? sender, ComponentEventArgs e)
    {
        string? name = e.Component!.Site!.Name;
        RemoveDeclaration(name);
    }
 
    /// <summary>
    ///  Raised by the host when a component is renamed. Here we dirty ourselves
    ///  and then whack the component declaration. At the next code gen
    ///  cycle we will recreate the declaration.
    /// </summary>
    private void OnComponentRename(object? sender, ComponentRenameEventArgs e)
    {
        OnComponentRename(e.Component!, e.OldName, e.NewName);
    }
 
    /// <summary>
    ///  Callback to create our demand-created services.
    /// </summary>
    private object? OnCreateService(IServiceContainer container, Type serviceType)
    {
        if (serviceType == typeof(ComponentSerializationService))
        {
            return new CodeDomComponentSerializationService(LoaderHost);
        }
 
        Debug.Fail("Called to create unknown service.");
 
        return null;
    }
 
    /// <summary>
    ///  This method should be called by the designer loader service
    ///  when all dependent loads have been completed. This
    ///  "shuts down" the loading process that was initiated by
    ///  BeginLoad. By default, the designer loader provides
    ///  IDesignerLoaderService itself, so this is called automatically.
    ///  If you provide your own loader service, or if you choose not
    ///  to provide a loader service, you are responsible for calling
    ///  this method. BeginLoad will automatically call this, either
    ///  indirectly by calling DependentLoadComplete if IDesignerLoaderService
    ///  is available, or directly if it is not.
    /// </summary>
    protected override void OnEndLoad(bool successful, ICollection? errors)
    {
        base.OnEndLoad(successful, errors);
 
        if (!successful)
        {
            return;
        }
 
        // After a successful load we will want to monitor a bunch of events so we know when
        // to make the loader dirty.
        if (!TryGetService(out IComponentChangeService? cs))
        {
            return;
        }
 
        cs.ComponentRemoved += OnComponentRemoved;
        cs.ComponentRename += OnComponentRename;
    }
 
    /// <summary>
    ///  This abstract method is called when it is time to
    ///  parse the source code and create a CodeDom tree.
    /// </summary>
    protected abstract CodeCompileUnit Parse();
 
    /// <summary>
    ///  Overrides BasicDesignerLoader's PerformFlush method to actual
    ///  write out the code dom tree.
    /// </summary>
    protected override void PerformFlush(IDesignerSerializationManager manager)
    {
        CodeTypeDeclaration? typeDeclaration = null;
 
        // Ask the serializer for the root component to serialize. This should return
        // a CodeTypeDeclaration, which we will plug into our existing code DOM tree.
        Debug.Assert(_rootSerializer is not null || _typeSerializer is not null, $"What are we saving right now?  Base component has no serializer: {LoaderHost.RootComponent.GetType().FullName}");
 
        if (_rootSerializer is not null)
        {
            typeDeclaration = _rootSerializer.Serialize(manager, LoaderHost.RootComponent) as CodeTypeDeclaration;
            Debug.Assert(typeDeclaration is not null, "Root CodeDom serializer must return a CodeTypeDeclaration");
        }
        else if (_typeSerializer is not null)
        {
            typeDeclaration = _typeSerializer.Serialize(manager, LoaderHost.RootComponent, LoaderHost.Container.Components);
        }
 
        // Now we must integrate the code DOM tree from the serializer with
        // our own tree. If changes were made to the tree this will
        // return true.
        if (typeDeclaration is not null && IntegrateSerializedTree(manager, typeDeclaration))
        {
            Write(_documentCompileUnit);
        }
    }
 
    /// <summary>
    ///  Overrides BasicDesignerLoader's PerformLoad method to deserialize the
    ///  classes from the code dom.
    /// </summary>
    protected override void PerformLoad(IDesignerSerializationManager manager)
    {
        // This ensures that all of the state for the document is available. This
        // will throw if state we need to load cannot be obtained.
        EnsureDocument(manager);
 
        // Ok, now we have a document, and a root serializer and a namespace. Or,
        // at least we better.
        Debug.Assert(_documentType is not null, "EnsureDocument didn't create a document type");
        Debug.Assert(_documentNamespace is not null, "EnsureDocument didn't create a document namespace");
        Debug.Assert(_rootSerializer is not null || _typeSerializer is not null, "EnsureDocument didn't create a root serializer");
 
        if (_rootSerializer is not null)
        {
            _rootSerializer.Deserialize(manager, _documentType);
        }
        else
        {
            _typeSerializer!.Deserialize(manager, _documentType);
        }
 
        SetBaseComponentClassName($"{_documentNamespace.Name}.{_documentType.Name}");
    }
 
    /// <summary>
    ///  This virtual method gets override in the VsCodeDomDesignerLoader to call the RenameElement on the
    ///  ChangeNotificationService to rename the component name through out the project scope.
    /// </summary>
    protected virtual void OnComponentRename(object component, string? oldName, string? newName)
    {
        if (LoaderHost.RootComponent == component)
        {
            if (_documentType is not null)
            {
                _documentType.Name = newName;
            }
 
            return;
        }
 
        if (_documentType is null)
        {
            return;
        }
 
        CodeTypeMemberCollection members = _documentType.Members;
 
        for (int i = 0; i < members.Count; i++)
        {
            if (members[i] is CodeMemberField field && members[i].Name.Equals(oldName)
                                              && field.Type.BaseType.Equals(TypeDescriptor.GetClassName(component)))
            {
                members[i].Name = newName;
                break;
            }
        }
    }
 
    /// <summary>
    ///  This is called when a component is deleted or renamed. We remove
    ///  the component's declaration here, if it exists.
    /// </summary>
    private void RemoveDeclaration(string? name)
    {
        if (_documentType is null)
        {
            return;
        }
 
        CodeTypeMemberCollection members = _documentType.Members;
 
        for (int i = 0; i < members.Count; i++)
        {
            if (members[i] is CodeMemberField && members[i].Name.Equals(name))
            {
                ((IList)members).RemoveAt(i);
                break;
            }
        }
    }
 
    /// <summary>
    ///  Determines of two type references are equal.
    /// </summary>
    private static bool TypesEqual(CodeTypeReference typeLeft, CodeTypeReference typeRight)
    {
        if (typeLeft.ArrayRank != typeRight.ArrayRank)
        {
            return false;
        }
 
        if (!typeLeft.BaseType.Equals(typeRight.BaseType))
        {
            return false;
        }
 
        if (typeLeft.TypeArguments is not null && typeRight.TypeArguments is null)
        {
            return false;
        }
 
        if (typeLeft.TypeArguments is null && typeRight.TypeArguments is not null)
        {
            return false;
        }
 
        if (typeLeft.TypeArguments is not null && typeRight.TypeArguments is not null)
        {
            if (typeLeft.TypeArguments.Count != typeRight.TypeArguments.Count)
            {
                return false;
            }
 
            for (int i = 0; i < typeLeft.TypeArguments.Count; i++)
            {
                if (!TypesEqual(typeLeft.TypeArguments[i], typeRight.TypeArguments[i]))
                {
                    return false;
                }
            }
        }
 
        if (typeLeft.ArrayRank > 0)
        {
            return TypesEqual(typeLeft.ArrayElementType!, typeRight.ArrayElementType!);
        }
 
        return true;
    }
 
    /// <summary>
    ///  This abstract method is called in response to a Flush
    ///  call when the designer loader is dirty. It will pass
    ///  a new CodeCompileUnit that represents the source code
    ///  needed to recreate the current designer's component
    ///  graph.
    /// </summary>
    protected abstract void Write(CodeCompileUnit unit);
 
    /// <summary>
    ///  Deserializes the provided serialization data object and
    ///  returns a collection of objects contained within that
    ///  data.
    /// </summary>
    ICollection IDesignerSerializationService.Deserialize(object serializationData)
    {
        if (serializationData is not SerializationStore data)
        {
            Exception ex = new ArgumentException(SR.CodeDomDesignerLoaderBadSerializationObject)
            {
                HelpLink = SR.CodeDomDesignerLoaderBadSerializationObject
            };
 
            throw ex;
        }
 
        ComponentSerializationService css = GetRequiredService<ComponentSerializationService>();
        return css.Deserialize(data, LoaderHost.Container);
    }
 
    /// <summary>
    ///  Serializes the given collection of objects and
    ///  stores them in an opaque serialization data object.
    ///  The returning object fully supports runtime serialization.
    /// </summary>
    object IDesignerSerializationService.Serialize(ICollection? objects)
    {
        objects ??= Array.Empty<object>();
 
        ComponentSerializationService css = GetRequiredService<ComponentSerializationService>();
        SerializationStore store = css.CreateStore();
 
        using (store)
        {
            foreach (object o in objects)
            {
                css.Serialize(store, o);
            }
        }
 
        return store;
    }
 
    /// <summary>
    ///  Creates a new name that is unique to all the components
    ///  in the given container. The name will be used to create
    ///  an object of the given data type, so the service may
    ///  derive a name from the data type's name.
    /// </summary>
    string INameCreationService.CreateName(IContainer? container, Type dataType)
    {
        ArgumentNullException.ThrowIfNull(dataType);
 
        string finalName;
 
        // Create a base member name that is a camel casing of the
        // data type name.
        string baseName = string.Create(dataType.Name.Length, dataType.Name, static (span, baseName) =>
        {
            for (int i = 0; i < baseName.Length; i++)
            {
                if (char.IsUpper(baseName[i]) && (i == 0 || i == baseName.Length - 1 || char.IsUpper(baseName[i + 1])))
                {
                    span[i] = char.ToLower(baseName[i], CultureInfo.CurrentCulture);
                }
                else
                {
                    baseName.AsSpan(i).Replace(span[i..], '`', '_');
                    break;
                }
            }
        });
 
        // Now hash up all of the member variable names using a case insensitive hash.
        CodeTypeDeclaration? type = _documentType;
        HashSet<string> memberHash = new(StringComparer.CurrentCultureIgnoreCase);
 
        if (type is not null)
        {
            foreach (CodeTypeMember member in type.Members)
            {
                memberHash.Add(member.Name);
            }
        }
 
        // VSWhidbey 95065: Only attempt to build a unique name here if we have a valid container
        // against which to check the result. Otherwise, the name build here might be appended to
        // with yet another iterator elsewhere. FWIW, this check is identical to the check used in
        // BaseDesignerLoader's implementation of INameCreationService.CreateName().
        if (container is not null)
        {
            int idx = 0;
            bool conflict;
 
            // Now loop until we find a name that hasn't been used.
            do
            {
                idx++;
                conflict = false;
                finalName = $"{baseName}{idx}";
 
                if (container.Components[finalName] is not null)
                {
                    conflict = true;
                }
 
                if (!conflict && memberHash.Contains(finalName))
                {
                    conflict = true;
                }
            }
            while (conflict);
        }
        else
        {
            finalName = baseName;
        }
 
        // And validate the new name against the code dom's code
        // generator to ensure it's not a keyword.
        if (_codeGenerator is null)
        {
            CodeDomProvider? provider = CodeDomProvider;
 
            if (provider is not null)
            {
                _codeGenerator = provider.CreateGenerator();
            }
        }
 
        if (_codeGenerator is not null)
        {
            finalName = _codeGenerator.CreateValidIdentifier(finalName);
        }
 
        return finalName;
    }
 
    /// <summary>
    ///  Determines if the given name is valid. A name
    ///  creation service may have rules defining a valid
    ///  name, and this method allows the service to enforce
    ///  those rules.
    /// </summary>
    bool INameCreationService.IsValidName(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
 
        if (name.Length == 0)
        {
            return false;
        }
 
        if (_codeGenerator is null)
        {
            CodeDomProvider? provider = CodeDomProvider;
 
            if (provider is not null)
            {
                _codeGenerator = provider.CreateGenerator();
            }
        }
 
        if (_codeGenerator is not null)
        {
            if (!_codeGenerator.IsValidIdentifier(name))
            {
                return false;
            }
 
            if (!_codeGenerator.IsValidIdentifier(name + "Handler"))
            {
                return false;
            }
        }
 
        // We don't validate against the type members if we're loading,
        // because during load these members are being added by the
        // parser, so of course there will be duplicates.
        if (!Loading)
        {
            CodeTypeDeclaration? type = _documentType;
 
            if (type is not null)
            {
                foreach (CodeTypeMember member in type.Members)
                {
                    if (string.Equals(member.Name, name, StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }
                }
            }
 
            // If the designer has been modified there is a chance that
            // the document type does not have all the necessary
            // members in it yet. So, we need to check the container
            // as well.
            if (Modified && LoaderHost.Container.Components[name] is not null)
            {
                return false;
            }
        }
 
        return true;
    }
 
    /// <summary>
    ///  Determines if the given name is valid. A name
    ///  creation service may have rules defining a valid
    ///  name, and this method allows the service to enforce
    ///  those rules. It is similar to IsValidName, except
    ///  that this method will throw an exception if the
    ///  name is invalid. This allows implementors to provide
    ///  rich information in the exception message.
    /// </summary>
    void INameCreationService.ValidateName(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
 
        if (name.Length == 0)
        {
            Exception ex = new ArgumentException(SR.CodeDomDesignerLoaderInvalidBlankIdentifier)
            {
                HelpLink = SR.CodeDomDesignerLoaderInvalidIdentifier
            };
 
            throw ex;
        }
 
        if (_codeGenerator is null)
        {
            CodeDomProvider? provider = CodeDomProvider;
 
            if (provider is not null)
            {
                _codeGenerator = provider.CreateGenerator();
            }
        }
 
        if (_codeGenerator is not null)
        {
            _codeGenerator.ValidateIdentifier(name);
 
            try
            {
                // We add something arbitrary and try to validate that. This is because we want to make sure
                // adding something is going to be fine, if not our event handler name generation will break in
                // case this identifier is something like a VB escaped keyword. For example, [public] is fine
                // but [public]_Click is not.
                _codeGenerator.ValidateIdentifier(name + "_");
            }
            catch
            {
                // we have to change the exception back to the original name
                Exception ex = new ArgumentException(string.Format(SR.CodeDomDesignerLoaderInvalidIdentifier, name))
                {
                    HelpLink = SR.CodeDomDesignerLoaderInvalidIdentifier
                };
 
                throw ex;
            }
        }
 
        if (Loading)
        {
            return;
        }
 
        // We don't validate against the type members if we're loading,
        // because during load these members are being added by the
        // parser, so of course there will be duplicates.
        bool dup = false;
        CodeTypeDeclaration? type = _documentType;
 
        if (type is not null)
        {
            foreach (CodeTypeMember member in type.Members)
            {
                if (string.Equals(member.Name, name, StringComparison.OrdinalIgnoreCase))
                {
                    dup = true;
                    break;
                }
            }
        }
 
        // If the designer has been modified there is a chance that
        // the document type does not have all the necessary
        // members in it yet. So, we need to check the container
        // as well.
        if (!dup && Modified && LoaderHost.Container.Components[name] is not null)
        {
            dup = true;
        }
 
        if (dup)
        {
            Exception ex = new ArgumentException(string.Format(SR.CodeDomDesignerLoaderDupComponentName, name))
            {
                HelpLink = SR.CodeDomDesignerLoaderDupComponentName
            };
 
            throw ex;
        }
    }
}