<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.Parser;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Controls.Infrastructure;
using DotVVM.Framework.Hosting;

namespace DotVVM.Framework.Runtime
{
    /// <summary>
    /// Builds the DotVVM view and resolves the master pages.
    /// </summary>
    public class DefaultDotvvmViewBuilder : IDotvvmViewBuilder
    {

        private IMarkupFileLoader markupFileLoader;

        private IControlBuilderFactory controlBuilderFactory;



        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDotvvmViewBuilder"/> class.
        /// </summary>
        public DefaultDotvvmViewBuilder(DotvvmConfiguration configuration)
        {
            markupFileLoader = configuration.ServiceLocator.GetService<IMarkupFileLoader>();
            controlBuilderFactory = configuration.ServiceLocator.GetService<IControlBuilderFactory>();
        }

        /// <summary>
        /// Builds the <see cref="DotvvmView"/> for the specified HTTP request, resolves the master page hierarchy and performs the composition.
        /// </summary>
        public DotvvmView BuildView(DotvvmRequestContext context)
        {
            // get the page markup
            var markup = markupFileLoader.GetMarkupFileVirtualPath(context);
            

            // build the page
            var pageBuilder = controlBuilderFactory.GetControlBuilder(markup);
            var contentPage = pageBuilder.BuildControl(controlBuilderFactory) as DotvvmView;

            FillsDefaultDirectives(contentPage, context.Configuration);

            // check for master page and perform composition recursively
            while (IsNestedInMasterPage(contentPage))
            {
                // load master page
                var masterPageFile = contentPage.Directives[ParserConstants.MasterPageDirective];
                var masterPage = (DotvvmView)controlBuilderFactory.GetControlBuilder(masterPageFile).BuildControl(controlBuilderFactory);

                FillsDefaultDirectives(masterPage, context.Configuration);
                PerformMasterPageComposition(contentPage, masterPage);

                masterPage.ViewModelType = contentPage.ViewModelType;
                contentPage = masterPage;
            }

            // verifies the SPA request
            VerifySpaRequest(context, contentPage);

            return contentPage;
        }

        /// <summary>
        /// If the request is SPA request, we need to verify that the page contains the same SpaContentPlaceHolder.
        /// Also we need to check that the placeholder is the same.
        /// </summary>
        private void VerifySpaRequest(DotvvmRequestContext context, DotvvmView page)
        {
            if (context.IsSpaRequest)
            {
                var spaContentPlaceHolders = page.GetAllDescendants().OfType<SpaContentPlaceHolder>().ToList();
                if (spaContentPlaceHolders.Count > 1)
                {
                    throw new Exception("Multiple controls of type <dot:SpaContentPlaceHolder /> found on the page! This control can be used only once!");   // TODO: exception handling
                }
                if (spaContentPlaceHolders.Count == 0 || spaContentPlaceHolders[0].GetSpaContentPlaceHolderUniqueId() != context.GetSpaContentPlaceHolderUniqueId())
                {
                    // the client has loaded different page which does not contain current SpaContentPlaceHolder - he needs to be redirected
                    context.RedirectToUrl(context.OwinContext.Request.Uri.AbsoluteUri);
                }
            }
        }

        /// <summary>
        /// Determines whether the page is nested in master page.
        /// </summary>
        private bool IsNestedInMasterPage(DotvvmView page)
        {
            return page.Directives.ContainsKey(ParserConstants.MasterPageDirective);
        }
        /// <summary>
        /// Fills default directives if specific directives are not set
        /// </summary>
        private void FillsDefaultDirectives(DotvvmView page, DotvvmConfiguration configuration)
        {
            foreach (var key in configuration.Markup.DefaultDirectives.Keys)
            {
                if (!page.Directives.Keys.Contains(key))
                {
                    page.Directives[key] = configuration.Markup.DefaultDirectives[key];
                }
            }
        }


        /// <summary>
        /// Performs the master page nesting.
        /// </summary>
        private void PerformMasterPageComposition(DotvvmView childPage, DotvvmView masterPage)
        {
            if (!masterPage.ViewModelType.IsAssignableFrom(childPage.ViewModelType))
                throw new DotvvmControlException(childPage, $"Master page requires viewModel of type '{masterPage.ViewModelType}' and it is not assignable from '{childPage.ViewModelType}'.");

            // find content place holders
            var placeHolders = GetMasterPageContentPlaceHolders(masterPage);

            // find contents
            var contents = GetChildPageContents(childPage, placeHolders);

            // perform the composition
            foreach (var content in contents)
            {
                // find the corresponding placeholder
                var placeHolder = placeHolders.SingleOrDefault(p => p.ID == content.ContentPlaceHolderID);
                if (placeHolder == null)
                {
                    throw new Exception(string.Format("The placeholder with ID '{0}' was not found in the master page!", content.ContentPlaceHolderID));
                }

                // replace the contents
                content.Parent.Children.Remove(content);
                placeHolder.Children.Clear();
                placeHolder.Children.Add(content);
                content.SetValue(Internal.IsMasterPageCompositionFinishedProperty, true);
                content.SetValue(DotvvmView.DirectivesProperty, childPage.Directives);
                content.SetValue(Internal.MarkupFileNameProperty, childPage.GetValue(Internal.MarkupFileNameProperty));
            }


            // copy the directives from content page to the master page (except the @masterpage)
            masterPage.ViewModelType = childPage.ViewModelType;
        }

        /// <summary>
        /// Gets the content place holders.
        /// </summary>
        private List<ContentPlaceHolder> GetMasterPageContentPlaceHolders(DotvvmControl masterPage)
        {
            var placeHolders = masterPage.GetAllDescendants().OfType<ContentPlaceHolder>().ToList();

            // check that no placeholder is nested in another one and that each one has valid ID<commit_msg>error message shows master page name<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.Parser;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Controls.Infrastructure;
using DotVVM.Framework.Hosting;

namespace DotVVM.Framework.Runtime
{
    /// <summary>
    /// Builds the DotVVM view and resolves the master pages.
    /// </summary>
    public class DefaultDotvvmViewBuilder : IDotvvmViewBuilder
    {

        private IMarkupFileLoader markupFileLoader;

        private IControlBuilderFactory controlBuilderFactory;



        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDotvvmViewBuilder"/> class.
        /// </summary>
        public DefaultDotvvmViewBuilder(DotvvmConfiguration configuration)
        {
            markupFileLoader = configuration.ServiceLocator.GetService<IMarkupFileLoader>();
            controlBuilderFactory = configuration.ServiceLocator.GetService<IControlBuilderFactory>();
        }

        /// <summary>
        /// Builds the <see cref="DotvvmView"/> for the specified HTTP request, resolves the master page hierarchy and performs the composition.
        /// </summary>
        public DotvvmView BuildView(DotvvmRequestContext context)
        {
            // get the page markup
            var markup = markupFileLoader.GetMarkupFileVirtualPath(context);
            

            // build the page
            var pageBuilder = controlBuilderFactory.GetControlBuilder(markup);
            var contentPage = pageBuilder.BuildControl(controlBuilderFactory) as DotvvmView;

            FillsDefaultDirectives(contentPage, context.Configuration);

            // check for master page and perform composition recursively
            while (IsNestedInMasterPage(contentPage))
            {
                // load master page
                var masterPageFile = contentPage.Directives[ParserConstants.MasterPageDirective];
                var masterPage = (DotvvmView)controlBuilderFactory.GetControlBuilder(masterPageFile).BuildControl(controlBuilderFactory);

                FillsDefaultDirectives(masterPage, context.Configuration);
                PerformMasterPageComposition(contentPage, masterPage);

                masterPage.ViewModelType = contentPage.ViewModelType;
                contentPage = masterPage;
            }

            // verifies the SPA request
            VerifySpaRequest(context, contentPage);

            return contentPage;
        }

        /// <summary>
        /// If the request is SPA request, we need to verify that the page contains the same SpaContentPlaceHolder.
        /// Also we need to check that the placeholder is the same.
        /// </summary>
        private void VerifySpaRequest(DotvvmRequestContext context, DotvvmView page)
        {
            if (context.IsSpaRequest)
            {
                var spaContentPlaceHolders = page.GetAllDescendants().OfType<SpaContentPlaceHolder>().ToList();
                if (spaContentPlaceHolders.Count > 1)
                {
                    throw new Exception("Multiple controls of type <dot:SpaContentPlaceHolder /> found on the page! This control can be used only once!");   // TODO: exception handling
                }
                if (spaContentPlaceHolders.Count == 0 || spaContentPlaceHolders[0].GetSpaContentPlaceHolderUniqueId() != context.GetSpaContentPlaceHolderUniqueId())
                {
                    // the client has loaded different page which does not contain current SpaContentPlaceHolder - he needs to be redirected
                    context.RedirectToUrl(context.OwinContext.Request.Uri.AbsoluteUri);
                }
            }
        }

        /// <summary>
        /// Determines whether the page is nested in master page.
        /// </summary>
        private bool IsNestedInMasterPage(DotvvmView page)
        {
            return page.Directives.ContainsKey(ParserConstants.MasterPageDirective);
        }
        /// <summary>
        /// Fills default directives if specific directives are not set
        /// </summary>
        private void FillsDefaultDirectives(DotvvmView page, DotvvmConfiguration configuration)
        {
            foreach (var key in configuration.Markup.DefaultDirectives.Keys)
            {
                if (!page.Directives.Keys.Contains(key))
                {
                    page.Directives[key] = configuration.Markup.DefaultDirectives[key];
                }
            }
        }


        /// <summary>
        /// Performs the master page nesting.
        /// </summary>
        private void PerformMasterPageComposition(DotvvmView childPage, DotvvmView masterPage)
        {
            if (!masterPage.ViewModelType.IsAssignableFrom(childPage.ViewModelType))
                throw new DotvvmControlException(childPage, $"Master page requires viewModel of type '{masterPage.ViewModelType}' and it is not assignable from '{childPage.ViewModelType}'.");

            // find content place holders
            var placeHolders = GetMasterPageContentPlaceHolders(masterPage);

            // find contents
            var contents = GetChildPageContents(childPage, placeHolders);

            // perform the composition
            foreach (var content in contents)
            {
                // find the corresponding placeholder
                var placeHolder = placeHolders.SingleOrDefault(p => p.ID == content.ContentPlaceHolderID);
                if (placeHolder == null)
                {
                    throw new DotvvmControlException(content, $"The placeholder with ID '{content.ContentPlaceHolderID}' was not found in the master page '{masterPage.GetValue(Internal.MarkupFileNameProperty)}'!");
                }

                // replace the contents
                content.Parent.Children.Remove(content);
                placeHolder.Children.Clear();
                placeHolder.Children.Add(content);
                content.SetValue(Internal.IsMasterPageCompositionFinishedProperty, true);
                content.SetValue(DotvvmView.DirectivesProperty, childPage.Directives);
                content.SetValue(Internal.MarkupFileNameProperty, childPage.GetValue(Internal.MarkupFileNameProperty));
            }


            // copy the directives from content page to the master page (except the @masterpage)
            masterPage.ViewModelType = childPage.ViewModelType;
        }

        /// <summary>
        /// Gets the content place holders.
        /// </summary>
        private List<ContentPlaceHolder> GetMasterPageContentPlaceHolders(DotvvmControl masterPage)
        {
            var placeHolders = masterPage.GetAllDescendants().OfType<ContentPlaceHolder>().ToList();

            // check that no placeholder is nested in another one and that each one has valid ID
{'fraction_non_alphanumeric': 0.07835739679315099, 'fraction_numerical': 0.0005078720162519045, 'mean_word_length': 2.2326454033771106, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5041196', 'n_tokens_mistral': 3385, 'n_tokens_neox': 3167, 'n_words': 817}