//------------------------------------------------------------------------------
// <copyright file="BuildProvider.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------



/*********************************

Class hierarchy

BuildProvider
    ProfileBuildProvider
    BaseResourcesBuildProvider
        ResXBuildProvider
        ResourcesBuildProvider
    XsdBuildProvider
    WsdlBuildProvider
    InternalBuildProvider
        SourceFileBuildProvider
        SimpleHandlerBuildProvider
            WebServiceBuildProvider
            WebHandlerBuildProvider
            ImageGeneratorBuildProvider
        BaseTemplateBuildProvider
            ApplicationBuildProvider
            TemplateControlBuildProvider
                PageBuildProvider
                UserControlBuildProvider
                    MasterPageBuildProvider
            PageThemeBuildProvider
                GlobalPageThemeBuildProvider

**********************************/


namespace System.Web.Compilation {

using System;
using System.Security.Permissions;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Web.Util;
using System.Web.UI;
using System.Web.Hosting;

// Flags returned from BuildProvider.GetResultFlags
[Flags]
public enum BuildProviderResultFlags {
    Default = 0,
    ShutdownAppDomainOnChange = 1,
}


/// <devdoc>
///    <para>
///       Base class for build providers that want to participate in a compilation.
///       It should be used by build providers that process files based on a virtual path.
///    </para>
/// </devdoc>
public abstract class BuildProvider {

    private static Dictionary<string, BuildProviderInfo> s_dynamicallyRegisteredProviders = new Dictionary<string, BuildProviderInfo>();

    //
    // Public interface
    //


    /// <devdoc>
    ///     Returns the CompilerType for the language that this build provider
    ///     needs to use, or null of it can use any language.
    /// </devdoc>
    public virtual CompilerType CodeCompilerType { get { return null; } }


    /// <devdoc>
    ///     Asks this build provider to generate any code that it needs, using the various
    ///     methods on the passed in BuildProvider.
    /// </devdoc>
    public virtual void GenerateCode(AssemblyBuilder assemblyBuilder) {}


    /// <devdoc>
    ///     Returns the class generated by this buildprovider
    /// </devdoc>
    public virtual Type GetGeneratedType(CompilerResults results) {
        return null;
    }


    /// <devdoc>
    ///     Returns a string that the provider wants to have persisted with the compiled assembly.
    /// </devdoc>
    public virtual string GetCustomString(CompilerResults results) {
        return null;
    }


    /// <devdoc>
    ///     Returns some flags that drives the behavior of the persisted result.
    /// </devdoc>
    public virtual BuildProviderResultFlags GetResultFlags(CompilerResults results) {
        return BuildProviderResultFlags.Default;
    }

    /// <devdoc>
    ///     Give the BuildProvider a chance to look at the compile errors, and possibly tweak them
    /// </devdoc>
    public virtual void ProcessCompileErrors(CompilerResults results) {
    }

    /// <devdoc>
    ///     Returns the list of files (virtual paths) that this provider depends on.
    ///     Those files need to be built before this BuildProvider.  The resulting assemblies
    ///     are added as references when compiling this BuildProvider.
    ///     This does not include things like server side includes, which don't directly
    ///     produce a BuildResult.
    ///     This is used to implement batching by separating BuildProviders
    /// </devdoc>
    internal virtual ICollection GetBuildResultVirtualPathDependencies() { return null; }

    public virtual ICollection VirtualPathDependencies {
        get {
            // By default, return the virtual path as its only dependency
            return new SingleObjectCollection(VirtualPath);
        }
    }


    /// <devdoc>
    ///     Returns the virtual path that this build provider handles
    /// </devdoc>
    protected internal string VirtualPath {
        get { return System.Web.VirtualPath.GetVirtualPathString(_virtualPath); }
    }


    /// <devdoc>
    ///     Returns the virtual path object that this build provider handles
    /// </devdoc>
    internal VirtualPath VirtualPathObject {
        get { return _virtualPath; }
    }


    /// <devdoc>
    ///     Opens a stream for the virtual file handled by this provider
    /// </devdoc>
    protected Stream OpenStream() {
        return OpenStream(VirtualPath);
    }


    /// <devdoc>
    ///     Opens a stream for a virtual file
    /// </devdoc>
    protected Stream OpenStream(string virtualPath) {
        return VirtualPathProvider.OpenFile(virtualPath);
    }

    internal /*protected*/ Stream OpenStream(VirtualPath virtualPath) {
        return virtualPath.OpenFile();
    }


    /// <devdoc>
    ///     Opens a reader for the virtual file handled by this provider
    /// </devdoc>
    protected TextReader OpenReader() {
        return OpenReader(VirtualPathObject);
    }


    /// <devdoc>
    ///     Opens a reader for a virtual file
    /// </devdoc>
    protected TextReader OpenReader(string virtualPath) {
        return OpenReader(System.Web.VirtualPath.Create(virtualPath));
    }

    internal /*protected*/ TextReader OpenReader(VirtualPath virtualPath) {
        Stream stream = OpenStream(virtualPath);
        return Util.ReaderFromStream(stream, virtualPath);
    }

    public static void RegisterBuildProvider(string extension, Type providerType) {
        if (String.IsNullOrEmpty(extension)) {
            throw ExceptionUtil.ParameterNullOrEmpty("extension");
        }
        if (providerType == null) {
            throw new ArgumentNullException("providerType");
        }
        if (!typeof(BuildProvider).IsAssignableFrom(providerType)) {
            // 
            throw ExceptionUtil.ParameterInvalid("providerType");
        }
        BuildManager.ThrowIfPreAppStartNotRunning();

        // Last call wins. If a user wants to use a different provider they can always provide an
        // override in the app's config.
        s_dynamicallyRegisteredProviders[extension] = new CompilationBuildProviderInfo(providerType);
    }

    internal static BuildProviderInfo GetBuildProviderInfo(System.Web.Configuration.CompilationSection config, string extension) {
        Debug.Assert(extension != null);
        var entry = config.BuildProviders[extension];
        if (entry != null) {
            return entry.BuildProviderInfo;
        }

        BuildProviderInfo info = null;
        s_dynamicallyRegisteredProviders.TryGetValue(extension, out info);
        return info;
    }


    /// <devdoc>
    ///     Returns a collection of assemblies that the build provider will be compiled with.
    /// </devdoc>
    protected ICollection ReferencedAssemblies {
        get { return _referencedAssemblies; }
    }


    /// <devdoc>
    ///     Returns the default CompilerType to be used for a specific language
    /// </devdoc>
    protected CompilerType GetDefaultCompilerTypeForLanguage(string language) {
        return CompilationUtil.GetCompilerInfoFromLanguage(VirtualPathObject, language);
    }


    /// <devdoc>
    ///     Returns the default CompilerType to be used for the default language
    /// </devdoc>
    protected CompilerType GetDefaultCompilerType() {
        return CompilationUtil.GetDefaultLanguageCompilerInfo(null /*compConfig*/, VirtualPathObject);
    }


    //
    // Internal code
    //

    #pragma warning disable 0649
    internal SimpleBitVector32 flags;
    #pragma warning restore 0649

    // const masks into the BitVector32
    internal const int isDependedOn                 = 0x00000001;
    internal const int noBuildResult                = 0x00000002;
    internal const int ignoreParseErrors            = 0x00000004;
    internal const int ignoreControlProperties      = 0x00000008;
    internal const int dontThrowOnFirstParseError   = 0x00000010;
    internal const int contributedCode              = 0x00000020;

    private VirtualPath _virtualPath;

    private ICollection _referencedAssemblies;

    private BuildProviderSet _buildProviderDependencies;
    internal BuildProviderSet BuildProviderDependencies {
        get {
            return _buildProviderDependencies;
        }
    }

    // Is any other BuildProvider depending on this BuildProvider
    internal bool IsDependedOn { get { return flags[isDependedOn]; } }

    internal void SetNoBuildResult() {
        flags[noBuildResult] = true;
    }

    // Remember that this BuildProvider has contributed to the compilation
    internal void SetContributedCode() {
        flags[contributedCode] = true;
    }

    internal void SetVirtualPath(VirtualPath virtualPath) {
        _virtualPath = virtualPath;
    }

    internal void SetReferencedAssemblies(ICollection referencedAssemblies) {
        _referencedAssemblies = referencedAssemblies;
    }

    internal void AddBuildProviderDependency(BuildProvider dependentBuildProvider) {
        if (_buildProviderDependencies == null)
            _buildProviderDependencies = new BuildProviderSet();

        _buildProviderDependencies.Add(dependentBuildProvider);

        dependentBuildProvider.flags[isDependedOn] = true;
    }

    /*
     * Return the culture name for this provider (e.g. "fr" or "fr-fr").
     * If no culture applies, return null.
     */
    internal string GetCultureName() {
        return Util.GetCultureName(VirtualPath);
    }

    internal BuildResult GetBuildResult(CompilerResults results) {

        BuildResult result = CreateBuildResult(results);
        if (result == null)
            return null;

        result.VirtualPath = VirtualPathObject;
        SetBuildResultDependencies(result);

        return result;
    }

    internal virtual BuildResult CreateBuildResult(CompilerResults results) {

        // If the build provider is not supposed to have a build result, just return
        if (flags[noBuildResult])
            return null;

        // Access the CompiledAssembly property to make sure the assembly gets loaded
        // Otherwise, the user code in GetGeneratedType() will fail to load it in medium trust since
        // they don't have access to the codegen folder
        if (!BuildManagerHost.InClientBuildManager && results != null) {
            // The ClientBuildManager runs in full trust, so we skip this statement 
            // to avoid unnecessary loading of the assembly.
            var assembly = results.CompiledAssembly;
        }

        // Ask the build provider if it wants to persist a Type
        Type type = GetGeneratedType(results);

        BuildResult result;

        if (type != null) {
            // Create a BuildResult for it
            BuildResultCompiledType resultCompiledType = CreateBuildResult(type);

            if (!resultCompiledType.IsDelayLoadType) {
                // If the returned type doesn't come from the generated assembly, set a flag
                if (results == null || type.Assembly != results.CompiledAssembly)
                    resultCompiledType.UsesExistingAssembly = true;
            }

            result = resultCompiledType;
        }
        else {
            // Ask the build provider if it instead wants to persist a custom string
            string customString = GetCustomString(results);

            // If it does, persist it
            if (customString != null) {
                // Only preserve an assembly if the BuildProvider has actually contributed code
                // to the compilation.
                result = new BuildResultCustomString(
                    flags[contributedCode] ? results.CompiledAssembly : null,
                    customString);
            }
            else {
                // Nothing was built: nothing to persist
                if (results == null)
                    return null;

                // Otherwise, just persist the assembly, if any
                result = new BuildResultCompiledAssembly(results.CompiledAssembly);
            }
        }

        // Ask the provider it it wants to set some flags on the result
        int resultFlags = (int) GetResultFlags(results);
        if (resultFlags != 0) {
            // Make sure only the lower bits are set
            Debug.Assert((resultFlags & 0xFFFF0000) == 0);
            resultFlags &= 0x0000FFFF;

            result.Flags |= resultFlags;
        }

        return result;
    }

    internal virtual BuildResultCompiledType CreateBuildResult(Type t) {
        return new BuildResultCompiledType(t);
    }

    internal void SetBuildResultDependencies(BuildResult result) {

        result.AddVirtualPathDependencies(VirtualPathDependencies);
    }


    //
    // Helper methods
    //

    internal static CompilerType GetCompilerTypeFromBuildProvider(
        BuildProvider buildProvider) {

        HttpContext context = null;
        if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure) && (context = HttpContext.Current) != null)
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_PARSE_ENTER, context.WorkerRequest);

        try {
            CompilerType compilerType = buildProvider.CodeCompilerType;
            if (compilerType != null) {
                CompilationUtil.CheckCompilerOptionsAllowed(compilerType.CompilerParameters.CompilerOptions,
                    false /*config*/, null, 0);
            }
            return compilerType;
        }
        finally {
            if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure) && context != null)
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_PARSE_LEAVE, context.WorkerRequest);
        }
    }

    //
    // Return a 'friendly' string that identifies the build provider
    //
    internal static string GetDisplayName(BuildProvider buildProvider) {

        // If it has a VirtualPath, use it
        if (buildProvider.VirtualPath != null) {
            return buildProvider.VirtualPath;
        }

        // Otherwise, the best we can do is the type name
        return buildProvider.GetType().Name;
    }

    internal virtual ICollection GetGeneratedTypeNames() {
        return null;
    }

    #region Methods from InternalBuildProvider

    // Protected internal methods: IgnoreParseErrors and GetCodeCompileUnit

    // When this is set, we ignore parse errors and keep on processing the page as
    // well as possible.  This is used for the Venus CBM scenario
    internal virtual bool IgnoreParseErrors {
        get { return flags[ignoreParseErrors]; }
        set { flags[ignoreParseErrors] = value; }
    }

    // This is used in CBM to instruct the control builders to skip control properties
    internal bool IgnoreControlProperties {
        get { return flags[ignoreControlProperties]; }
        set { flags[ignoreControlProperties] = value; }
    }

    // Indicates whether the parser should continue processing for more errors.
    // This is used in both CBM and aspnet_compiler tool.
    internal bool ThrowOnFirstParseError {
        get { return !flags[dontThrowOnFirstParseError]; }
        set { flags[dontThrowOnFirstParseError] = !value; }
    }

    // This is used in the CBM scenario only
    internal virtual IAssemblyDependencyParser AssemblyDependencyParser {
        get { return null; }
    }

    // This is used in the CBM scenario only
    /// <devdoc>
    /// Returns the CodeCompileUnit and sets the associated dictionary containing the line pragmas.
    /// </devdoc>
    protected internal virtual CodeCompileUnit GetCodeCompileUnit(out IDictionary linePragmasTable) {

        // Default implementation with code at line 1

        string sourceString = Util.StringFromVirtualPath(VirtualPathObject);
        CodeSnippetCompileUnit snippetCompileUnit = new CodeSnippetCompileUnit(sourceString);

        LinePragmaCodeInfo codeInfo = new LinePragmaCodeInfo(1 /* startLine */, 1 /* startColumn */, 1 /* startGeneratedColumn */, -1 /* codeLength */, false /* isCodeNuggest */);
        linePragmasTable = new Hashtable();
        linePragmasTable[1] = codeInfo;

        return snippetCompileUnit;
    }

    internal virtual ICollection GetCompileWithDependencies() { return null; }

    #endregion Methods from InternalBuildProvider

    private class CompilationBuildProviderInfo : BuildProviderInfo {

        private readonly Type _type;

        public CompilationBuildProviderInfo(Type type) {
            Debug.Assert(type != null);
            _type = type;
        }

        internal override Type Type {
            get {
                return _type;
            }
        }
    }
}

// The InternalBuildProvider class is still retained to internally distinguish between
// buildProviders that actually implement the required methods and those that don't, and
// also to minimize code change where possible.
internal abstract class InternalBuildProvider: BuildProvider {

}

internal abstract class BuildProviderInfo {
    // AppliesTo value from the BuildProviderAppliesToAttribute
    private BuildProviderAppliesTo _appliesTo;

    internal abstract Type Type { get; }

    internal BuildProviderAppliesTo AppliesTo {
        get {
            if (_appliesTo != 0)
                return _appliesTo;

            // Check whether the control builder's class exposes an AppliesTo attribute
            object[] attrs = Type.GetCustomAttributes(
                typeof(BuildProviderAppliesToAttribute), /*inherit*/ true);

            if ((attrs != null) && (attrs.Length > 0)) {
                Debug.Assert(attrs[0] is BuildProviderAppliesToAttribute);
                _appliesTo = ((BuildProviderAppliesToAttribute)attrs[0]).AppliesTo;
            }
            else {
                // Default to applying to All
                _appliesTo = BuildProviderAppliesTo.All;
            }

            return _appliesTo;
        }
    }
}

}
