File: MS\Internal\Documents\Application\DocumentManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationUI\PresentationUI_eofwyw2p_wpftmp.csproj (PresentationUI)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// Description:
// Exposes the basic operations that can be performed on documents. (Open,
// EnableEdit, Save)
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
 
namespace MS.Internal.Documents.Application
{
/// <summary>
/// Exposes the basic operations that can be performed on documents. (Open,
/// EnableEdit, Save)
/// </summary>
/// <remarks>
/// Responsibility:
/// The class is responsible for delegating the work to the appropriate
/// controller(s) in the order needed based on document dependencies.
/// 
/// Design Comments:
/// Packages are dependent on EncryptedPackages who are dependent on FileStreams
/// however all these classes are very different in function.
/// 
/// By design once a controller has reported handling a document it will not be
/// given to other controllers.  A controller should not see documents they are
/// not in the dependency chain for.
/// 
/// Example: FileController should never see RightsDocument.
/// </remarks>
internal sealed class DocumentManager 
    : ChainOfResponsiblity<IDocumentController, Document>
{
    #region Constructors
    //--------------------------------------------------------------------------
    // Constructors
    //--------------------------------------------------------------------------
 
    /// <summary>
    /// Will construct a DocumentManager using the provided controllers.
    /// </summary>
    /// <param name="controllers">Controllers in order of responsiblity from
    /// top to bottom.</param>
    internal DocumentManager(params IDocumentController[] controllers)
        : base(controllers) { }
    #endregion Constructors
 
    #region Internal Methods
    //--------------------------------------------------------------------------
    // Internal Methods
    //--------------------------------------------------------------------------
 
    /// <summary>
    /// Creates the default chain of controllers.  (PackageController, 
    /// RightsController and FileController)
    /// </summary>
    /// <returns>A DocumentManager.</returns>
    /// <remarks>
    /// This exists because of a a design compromise; see SaveAs below.
    ///
    /// This compromise breaks encapsulation, because we have an understanding of
    /// begin used within a navigation application.
    /// </remarks>
    internal static DocumentManager CreateDefault()
    {
        if (_singleton == null)
        {
            _controllers.Add(new HostedController());
            _controllers.Add(new PackageController());
            _controllers.Add(new RightsController());
            _controllers.Add(new FileController());
 
            _singleton = new DocumentManager(
                _controllers.ToArray());
 
            Trace.SafeWrite(Trace.File, "DocumentManager singleton created.");
        }
 
        return _singleton;
    }
 
    /// <summary>
    /// Creates the default chain of documents.  (PackageDocument, 
    /// RightsDocument and FileDocument)
    /// </summary>
    /// <returns>A Document.</returns>
    internal static PackageDocument CreateDefaultDocument(
        Uri source, CriticalFileToken fileToken)
    {
        // because we have a fileToken we might be able to save
        _canSave = true;
 
        PackageDocument doc = new PackageDocument(
            new RightsDocument(
            new FileDocument(fileToken)));
 
        doc.Uri = source;
 
        return doc;
    }
 
    /// <summary>
    /// Creates the default chain of documents.  (PackageDocument, 
    /// RightsDocument and FileDocument)
    /// </summary>
    /// <returns>A Document.</returns>
    internal static PackageDocument CreateDefaultDocument(
        Uri source, Stream stream)
    {
        PackageDocument doc = new PackageDocument(
            new RightsDocument(
            new FileDocument(stream)));
 
        doc.Uri = source;
 
        return doc;
    }
 
    /// <summary>
    /// Allows IDocumentControllers to be properly cleaned up when created
    /// by the factory method of this class.
    /// </summary>
    internal static void CleanUp()
    {
        foreach (IDocumentController controller in _controllers)
        {
            IDisposable disposable = controller as IDisposable;
 
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }
    }
 
    /// <summary>
    /// Will enable editing for the document.
    /// </summary>
    /// <param name="document">A document.</param>
    internal void EnableEdit(Document document)
    {
        if (document == null)
        {
            document = _current;
        }
 
        if (!_isEditEnabled)
        {
            _isEditEnabled = OrderByLeastDependent(DispatchEnableEdit, document);
        }
    }
 
    /// <summary>
    /// Will open the specified document.
    /// </summary>
    /// <param name="document">A document.</param>
    /// <returns>True if the operation succeeded</returns>
    internal bool Open(Document document)
    {
        ThrowIfNull(document);
 
        _current = document;
        // since we just loaded the document, it is now unmodified from the saved version
        _isModified = false; 
        return OrderByLeastDependent(DispatchOpen, document);
    }
 
    /// <summary>
    /// Will save the current document to a user-specified location.
    /// </summary>
    /// <returns>True if the operation succeeded</returns>
    internal bool SaveAs(Document document)
    {
        if (document == null)
        {
            document = _current;
        }
 
        bool result = OrderByLeastDependent(DispatchSaveAsPreperation, document);
 
        if (result)
        {
            result = OrderByMostDependent(DispatchSaveCommit, document);
        }
 
        if (result)
        {
            // since we just saved the document, it is now unmodified from the saved version
            _isModified = false;
            result = OrderByLeastDependent(DispatchRebind, document); 
        }
 
        return result;
    }
 
    /// <summary>
    /// Will save the specified document.
    /// </summary>
    /// <param name="document">A document.</param>
    /// <returns>True if the operation succeeded</returns>
    internal bool Save(Document document)
    {
        if (document == null)
        {
            document = _current;
        }
 
        bool result = OrderByLeastDependent(DispatchSavePreperation, document);
 
        if (result)
        {
            result = OrderByMostDependent(DispatchSaveCommit, document);
        }
        
        // We always Rebind even if the Commit failed -- if we fail we still 
        // need to Rebind the original document.
        OrderByLeastDependent(DispatchRebind, document);
        // since we just saved the document, it is now unmodified from the saved version
        _isModified = false;
 
        return result;
    }
 
    /// <summary>
    /// This event handler is called to notify us that the document was modified.
    /// </summary>
    /// <param name="sender">sender of the event (not used)</param>
    /// <param name="args">arguments of the event (not used)</param>
    internal static void OnModify(Object sender, EventArgs args)
    {
        _isModified = true;
    }
 
    /// <summary>
    /// Forces the given document to reload.
    /// </summary>
    /// <param name="document">The document</param>
    /// <returns>True if the operation succeeded</returns>
    internal bool Reload(Document document)
    {
        if (document == null)
        {
            document = _current;
        }
 
        // Set IsReloadNeeded to force reloading the document
        document.IsReloadNeeded = true;
 
        // Dispatch a rebind operation, which will cause a reload
        return OrderByLeastDependent(DispatchRebind, document);
    }
 
    #endregion Internal Methods
 
    #region Internal Properties
    //--------------------------------------------------------------------------
    // Internal Properties
    //--------------------------------------------------------------------------
 
    /// <summary>
    /// Gets or sets whether saving to the source of the document may be possible.
    /// </summary>
    /// <remarks>
    /// Currently we only support saving when opened by a file Uri.
    /// </remarks>
    internal bool CanSave
    {
        get
        {
            return _canSave;
        }
        set
        {
            _canSave = value;
        }
    }
 
    /// <summary>
    /// This property returns true if the document has been modified from the
    /// version on disk (and therefore it makes sense for the user to save it).
    /// This property combines modification information from multiple sources.
    /// </summary>
    /// <remarks>
    /// Some changes are reflected in Package.IsDirty -- all other changes
    /// will notify us by writing to this property.  The document is considered
    /// modified if it is modified either according to the PackageDocument or
    /// our internal flag.
    /// </remarks>
    internal bool IsModified
    {
        get
        {
            PackageDocument doc = _current as PackageDocument;
            // If we don't have a PackageDocument (for example, if we are viewing 
            // a read-only file), then only our internal flag is used.
            if (doc == null || doc.Package == null)
            {
                return _isModified;
            }
            else
            {
                return doc.Package.IsDirty || _isModified;
            }
        }
    }
 
    #endregion Internal Properties
 
    #region Private Methods
    //--------------------------------------------------------------------------
    // Private Methods
    //--------------------------------------------------------------------------
 
    /// <summary>
    /// Invokes EnableEdit on the controller provided using the document given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchEnableEdit
        (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Workspace == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping EnableEdit for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.EnableEdit(document);
    }
 
    /// <summary>
    /// Invokes Open on the controller provided using the document given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchOpen
        (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Source == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping Open for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.Open(document);
    }
 
    /// <summary>
    /// Invokes Rebind on the controller provided using the document given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchRebind
    (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Source == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping Rebind for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.Rebind(document);
    }
 
    /// <summary>
    /// Invokes SaveAsPreperation on the controller provided using the document
    /// given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchSaveAsPreperation
        (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Destination == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping SaveAsPreperation for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.SaveAsPreperation(document);
    }
 
    /// <summary>
    /// Invokes SaveCommit on the controller provided using the document given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchSaveCommit
        (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Destination == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping SaveCommit for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.SaveCommit(document);
    }
 
    /// <summary>
    /// Invokes SavePreperation on the controller provided using the document
    /// given.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private static bool DispatchSavePreperation
        (IDocumentController controller, Document document)
    {
        if ((document.Dependency != null) && (document.Dependency.Destination == null))
        {
            Trace.SafeWrite(
                Trace.File,
                "Skipping SavePreperation for {0} because dependent Stream is null.",
                controller);
            return true;
        }
        return controller.SavePreperation(document);
    }
 
    /// <summary>
    /// Invokes the action from the top of the document dependency chain to the
    /// bottom, for each provider under it in the chain.
    /// </summary>
    /// <remarks>
    /// OrderByMostDependent is implement in the ChainOfDependency
    /// (not a GoF pattern), it walks the tree of dependencies in this case the
    /// most dependent would be first.  The Chain of Responsiblity pattern is
    /// invoke by the inherited member 'Dispatch' which is called for each
    /// dependency.
    /// </remarks>
    /// <param name="action">The action to perform.</param>
    /// <param name="document">The document to perform it on.</param>
    private bool OrderByMostDependent(DispatchDelegate action, Document document)
    {
        return ChainOfDependencies<Document>.OrderByMostDependent(
            document,
            delegate(Document member)
            {
                return this.Dispatch(delegate(
                    IDocumentController controller,
                    Document subject)
                {
                    return action(controller, subject);
                },
                member);
            });
    }
 
    /// <summary>
    /// Invokes the action from the bottom of the document dependency chain up
    /// to the top, for each provider under it in the chain.
    /// </summary>
    /// <remarks>
    /// OrderByLeastDependent is implement in the ChainOfDependency
    /// (not a GoF pattern), it walks the tree of dependencies in this case the
    /// least dependent would be first.  The Chain of Responsiblity pattern is
    /// invoke by the inherited member 'Dispatch' which is called for each
    /// dependency.
    /// </remarks>
    /// <param name="action">The action to perform.</param>
    /// <param name="document">The document to perform it on.</param>
    private bool OrderByLeastDependent(DispatchDelegate action, Document document)
    {
        return ChainOfDependencies<Document>.OrderByLeastDependent(
            document,
            delegate(Document member)
            {
                return this.Dispatch(delegate(
                    IDocumentController controller,
                    Document subject)
                {
                    return action(controller, subject);
                },
                member);
            });
    }
 
    /// <summary>
    /// Will throw if the document is null.
    /// </summary>
    /// <exception cref="System.ArgumentNullException"/>
    /// <param name="document">The document to validate.</param>
    private static void ThrowIfNull(Document document)
    {
        ArgumentNullException.ThrowIfNull(document);
    }
    #endregion Private Methods
 
    #region Private Delegates
    //--------------------------------------------------------------------------
    // Private Delegates
    //--------------------------------------------------------------------------
 
    /// <summary>
    /// Defines the delegate for actions invoked by DocumentManager.
    /// </summary>
    /// <param name="controller">The controller to perform the action.</param>
    /// <param name="document">The document to perform it on.</param>
    /// <returns>True if handled by controller.</returns>
    private delegate bool DispatchDelegate(
        IDocumentController controller, Document document);
    #endregion Private Delegates
 
    #region Private Fields
    //--------------------------------------------------------------------------
    // Private Fields
    //--------------------------------------------------------------------------
 
    // Fields below simply prevents undesired user experience, they do not
    // impact security constraints nor are they used as protection
 
    private static Document _current;
    private static DocumentManager _singleton;
    private static bool _canSave;
    private static bool _isEditEnabled;
    private static bool _isModified;
    private static List<IDocumentController> _controllers =
        new List<IDocumentController>();
    #endregion Private Fields
}
}