File: System\Activities\Runtime\BookmarkManager.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.Runtime
{
    using System;
    using System.Activities.Hosting;
    using System.Collections.Generic;
    using System.Runtime;
    using System.Runtime.Serialization;
 
    [DataContract(Name = XD.Runtime.BookmarkManager, Namespace = XD.Runtime.Namespace)]
    class BookmarkManager
    {
        long nextId;
 
        Dictionary<Bookmark, BookmarkCallbackWrapper> bookmarks;
 
        BookmarkScope scope;
 
        BookmarkScopeHandle scopeHandle;
 
        public BookmarkManager()
        {
            this.nextId = 1;
        }
 
        internal BookmarkManager(BookmarkScope scope, BookmarkScopeHandle scopeHandle)
            : this()
        {
            this.scope = scope;
            this.scopeHandle = scopeHandle;
        }
 
 
        public bool HasBookmarks
        {
            get
            {
                return this.bookmarks != null && this.bookmarks.Count > 0;
            }
        }
 
        [DataMember(Name = "nextId")]
        internal long SerializedNextId
        {
            get { return this.nextId; }
            set { this.nextId = value; }
        }
 
        [DataMember(EmitDefaultValue = false, Name = "bookmarks")]
        internal Dictionary<Bookmark, BookmarkCallbackWrapper> SerializedBookmarks
        {
            get { return this.bookmarks; }
            set { this.bookmarks = value; }
        }
 
        [DataMember(EmitDefaultValue = false, Name = "scope")]
        internal BookmarkScope SerializedScope
        {
            get { return this.scope; }
            set { this.scope = value; }
        }
 
        [DataMember(EmitDefaultValue = false, Name = "scopeHandle")]
        internal BookmarkScopeHandle SerializedScopeHandle
        {
            get { return this.scopeHandle; }
            set { this.scopeHandle = value; }
        }
 
        public Bookmark CreateBookmark(string name, BookmarkCallback callback, ActivityInstance owningInstance, BookmarkOptions options)
        {
            Bookmark toAdd = new Bookmark(name);
 
            if (this.bookmarks != null && this.bookmarks.ContainsKey(toAdd))
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.BookmarkAlreadyExists(name)));
            }
 
            AddBookmark(toAdd, callback, owningInstance, options);
            //Regular bookmarks are never important
            UpdateAllExclusiveHandles(toAdd, owningInstance);
 
            return toAdd;
        }
 
        public Bookmark CreateBookmark(BookmarkCallback callback, ActivityInstance owningInstance, BookmarkOptions options)
        {
            Fx.Assert(this.scope == null, "We only support named bookmarks within bookmark scopes right now.");
 
            Bookmark bookmark = Bookmark.Create(GetNextBookmarkId());
            AddBookmark(bookmark, callback, owningInstance, options);
            //Regular bookmarks are never important
            UpdateAllExclusiveHandles(bookmark, owningInstance);
 
            return bookmark;
        }
 
        void UpdateAllExclusiveHandles(Bookmark bookmark, ActivityInstance owningInstance)
        {
            Fx.Assert(bookmark != null, "Invalid call to UpdateAllExclusiveHandles. Bookmark was null");
            Fx.Assert(owningInstance != null, "Invalid call to UpdateAllExclusiveHandles. ActivityInstance was null");
 
            if (owningInstance.PropertyManager == null)
            {
                return;
            }
 
            if (!owningInstance.PropertyManager.HasExclusiveHandlesInScope)
            {
                return;
            }
 
            List<ExclusiveHandle> handles = owningInstance.PropertyManager.FindAll<ExclusiveHandle>();
 
            if (handles == null)
            {
                return;
            }
 
            for (int i = 0; i < handles.Count; i++)
            {
                ExclusiveHandle handle = handles[i];
                if (handle != null)
                {
                    if (this.scopeHandle != null)
                    {
                        bool found = false;
                        foreach (BookmarkScopeHandle bookmarkScopeHandle in handle.RegisteredBookmarkScopes)
                        {
                            if (bookmarkScopeHandle == this.scopeHandle)
                            {
                                handle.AddToImportantBookmarks(bookmark);
                                found = true;
                                break;
                            }
                        }
                        if (!found)
                        {
                            handle.AddToUnimportantBookmarks(bookmark);
                        }
                    }
                    else
                    {
                        handle.AddToUnimportantBookmarks(bookmark);
                    }
                }
            }
        }
 
        public Bookmark GenerateTempBookmark()
        {
            return Bookmark.Create(GetNextBookmarkId());
        }
 
        void AddBookmark(Bookmark bookmark, BookmarkCallback callback, ActivityInstance owningInstance, BookmarkOptions options)
        {
            if (this.bookmarks == null)
            {
                this.bookmarks = new Dictionary<Bookmark, BookmarkCallbackWrapper>(Bookmark.Comparer);
            }
 
            bookmark.Scope = this.scope;
 
            BookmarkCallbackWrapper bookmarkCallbackWrapper = new BookmarkCallbackWrapper(callback, owningInstance, options)
            {
                Bookmark = bookmark
            };
            this.bookmarks.Add(bookmark, bookmarkCallbackWrapper);
 
            owningInstance.AddBookmark(bookmark, options);
 
            if (TD.CreateBookmarkIsEnabled())
            {
                TD.CreateBookmark(owningInstance.Activity.GetType().ToString(), owningInstance.Activity.DisplayName, owningInstance.Id, ActivityUtilities.GetTraceString(bookmark), ActivityUtilities.GetTraceString((BookmarkScope)bookmark.Scope));
            }
        }
 
        long GetNextBookmarkId()
        {
            if (this.nextId == long.MaxValue)
            {
                throw FxTrace.Exception.AsError(new NotSupportedException(SR.OutOfInternalBookmarks));
            }
 
            long result = this.nextId;
            this.nextId++;
            return result;
        }
 
        // This method translates from a bookmark that we may have received from the outside world (IE - new Bookmark(someName))
        // to our internal Bookmark object.  This is necessary because we use bookmark objects as the key to our dictionary
        // (hence our ability to resolve an externally created one), but we keep a lot of important information on our internal
        // instance of that bookmark.  We must always perform this translation when doing exclusive handle housekeeping.
        public bool TryGetBookmarkFromInternalList(Bookmark bookmark, out Bookmark internalBookmark, out BookmarkCallbackWrapper callbackWrapper)
        {
            internalBookmark = null;
            callbackWrapper = null;
            if (this.bookmarks == null)
            {
                return false;
            }
 
            BookmarkCallbackWrapper wrapper;
            if (this.bookmarks.TryGetValue(bookmark, out wrapper))
            {
                internalBookmark = wrapper.Bookmark;
                callbackWrapper = wrapper;
                return true;
            }
 
            return false;
        }
 
        public BookmarkResumptionResult TryGenerateWorkItem(ActivityExecutor executor, bool isExternal, ref Bookmark bookmark, object value, ActivityInstance isolationInstance, out ActivityExecutionWorkItem workItem)
        {
            Bookmark internalBookmark = null;
            BookmarkCallbackWrapper callbackWrapper = null;
            if (!this.TryGetBookmarkFromInternalList(bookmark, out internalBookmark, out callbackWrapper))
            {
                workItem = null;
                return BookmarkResumptionResult.NotFound;
            }
 
            bookmark = internalBookmark;
            if (!ActivityUtilities.IsInScope(callbackWrapper.ActivityInstance, isolationInstance))
            {
                workItem = null;
 
                // We know about the bookmark, but we can't resume it yet
                return BookmarkResumptionResult.NotReady;
            }
 
            workItem = callbackWrapper.CreateWorkItem(executor, isExternal, bookmark, value);
 
            if (!BookmarkOptionsHelper.SupportsMultipleResumes(callbackWrapper.Options))
            {
                // cleanup bookmark on resumption unless the user opts into multi-resume
                Remove(bookmark, callbackWrapper);
            }
 
            return BookmarkResumptionResult.Success;
        }
 
        public void PopulateBookmarkInfo(List<BookmarkInfo> bookmarks)
        {
            Fx.Assert(this.HasBookmarks, "Should only be called if this actually has bookmarks.");
 
            foreach (KeyValuePair<Bookmark, BookmarkCallbackWrapper> bookmarkEntry in this.bookmarks)
            {
                if (bookmarkEntry.Key.IsNamed)
                {
                    bookmarks.Add(bookmarkEntry.Key.GenerateBookmarkInfo(bookmarkEntry.Value));
                }
            }
        }
 
        // No need to translate using TryGetBookmarkFromInternalList because we already have
        // internal instances since this call comes from bookmarks hanging off of the
        // ActivityInstance and not from an external source
        public void PurgeBookmarks(Bookmark singleBookmark, IList<Bookmark> multipleBookmarks)
        {
            if (singleBookmark != null)
            {
                PurgeSingleBookmark(singleBookmark);
            }
            else
            {
                Fx.Assert(multipleBookmarks != null, "caller should never pass null");
                for (int i = 0; i < multipleBookmarks.Count; i++)
                {
                    Bookmark bookmark = multipleBookmarks[i];
                    PurgeSingleBookmark(bookmark);
                }
            }
        }
 
        internal void PurgeSingleBookmark(Bookmark bookmark)
        {
            Fx.Assert(this.bookmarks.ContainsKey(bookmark) && object.ReferenceEquals(bookmark, this.bookmarks[bookmark].Bookmark), "Something went wrong with our housekeeping - it must exist and must be our intenral reference");
            UpdateExclusiveHandleList(bookmark);
            this.bookmarks.Remove(bookmark);
        }
 
        public bool Remove(Bookmark bookmark, ActivityInstance instanceAttemptingRemove)
        {
            // We need to translate to our internal instance of the bookmark.  See TryGetBookmarkFromInternalList
            // for more details.
            BookmarkCallbackWrapper callbackWrapper;
            Bookmark internalBookmark;
            if (TryGetBookmarkFromInternalList(bookmark, out internalBookmark, out callbackWrapper))
            {
                if (callbackWrapper.ActivityInstance != instanceAttemptingRemove)
                {
                    throw FxTrace.Exception.AsError(new InvalidOperationException(SR.OnlyBookmarkOwnerCanRemove));
                }
 
                Remove(internalBookmark, callbackWrapper);
                return true;
            }
            else
            {
                return false;
            }
        }
 
        void Remove(Bookmark bookmark, BookmarkCallbackWrapper callbackWrapper)
        {
            callbackWrapper.ActivityInstance.RemoveBookmark(bookmark, callbackWrapper.Options);
            UpdateExclusiveHandleList(bookmark);
            this.bookmarks.Remove(bookmark);
        }
 
        void UpdateExclusiveHandleList(Bookmark bookmark)
        {
            if (bookmark.ExclusiveHandles != null)
            {
                for (int i = 0; i < bookmark.ExclusiveHandles.Count; i++)
                {
                    ExclusiveHandle handle = bookmark.ExclusiveHandles[i];
                    Fx.Assert(handle != null, "Internal error.. ExclusiveHandle was null");
                    handle.RemoveBookmark(bookmark);
                }
            }
        }
    }
}