File: MS\Internal\Annotations\Storage\StoreAnnotationsMap.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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:
//     Map of annotation instances handed out by a particular store.
//     Used to guarantee only one instance of an annotation is ever
//     produced.  Also register for notifications on the instances
//     and passes change notifications on to the store.
//
//     Spec: CAF Storage Spec.doc
//
 
using System.Windows.Annotations;
 
 
namespace MS.Internal.Annotations.Storage
{
    /// <summary>
    /// A map the store uses to maintain having one instance 
    /// of the object model
    /// </summary>
    internal class StoreAnnotationsMap
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// Constructor for StoreAnnotationsMap 
        /// It takes instances of the AnnotationAuthorChangedEventHandler, AnnotationResourceChangedEventHandler, AnnotationResourceChangedEventHandler delegates
        /// </summary>
        /// <param name="authorChanged">delegate used to register for AuthorChanged events on all annotations</param>
        /// <param name="anchorChanged">delegate used to register for AnchorChanged events on all annotations</param>
        /// <param name="cargoChanged">delegate used to register for CargoChanged events on all annotations</param>
        internal StoreAnnotationsMap(AnnotationAuthorChangedEventHandler authorChanged, AnnotationResourceChangedEventHandler anchorChanged, AnnotationResourceChangedEventHandler cargoChanged)
        {
            Debug.Assert(authorChanged != null && anchorChanged != null && cargoChanged != null,
                         "Author and Anchor and Cargo must not be null");
 
            _authorChanged = authorChanged;
            _anchorChanged = anchorChanged;
            _cargoChanged = cargoChanged;
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// Add an annotation to the map
        /// throws an exception if the annotation already exists
        /// </summary>
        /// <param name="annotation">instance of annotation to add to the map</param>
        /// <param name="dirty">the initial dirty flag of the annotation in the map</param>
        public void AddAnnotation(Annotation annotation, bool dirty)
        {
            Debug.Assert(FindAnnotation(annotation.Id) == null, "annotation  not found");
            annotation.AuthorChanged += OnAuthorChanged;
            annotation.AnchorChanged += OnAnchorChanged;
            annotation.CargoChanged += OnCargoChanged;
            _currentAnnotations.Add(annotation.Id, new CachedAnnotation(annotation, dirty));
        }
 
        /// <summary>
        /// remove an annotation from the map if it exists
        /// otherwise return
        /// </summary>
        /// <param name="id">id of the annotation to remove</param>
        public void RemoveAnnotation(Guid id)
        {
            CachedAnnotation existingCachedAnnot = null;
            if (_currentAnnotations.TryGetValue(id, out existingCachedAnnot))
            {
                existingCachedAnnot.Annotation.AuthorChanged -= OnAuthorChanged;
                existingCachedAnnot.Annotation.AnchorChanged -= OnAnchorChanged;
                existingCachedAnnot.Annotation.CargoChanged -= OnCargoChanged;
                _currentAnnotations.Remove(id);
            }
        }
 
        /// <summary>
        ///     Queries the store map for annotations that have an anchor
        ///     that contains a locator that begins with the locator parts
        ///     in anchorLocator.
        /// </summary>
        /// <param name="anchorLocator">the locator we are looking for</param>
        /// <returns>
        ///    A list of annotations that have locators in their anchors
        ///    starting with the same locator parts list as of the input locator
        ///    If no such annotations an empty list will be returned. The method
        ///    never returns null.
        /// </returns>
        public Dictionary<Guid, Annotation> FindAnnotations(ContentLocator anchorLocator)
        {
            ArgumentNullException.ThrowIfNull(anchorLocator);
 
            Dictionary<Guid, Annotation> annotations = new Dictionary<Guid, Annotation>();
 
            // In the future, this probably has to be done in a way more effecient than linear search 
            // the plan of record is to start with this and improve later
            // the map is already solving a perf bottleneck for tablet
            Dictionary<Guid, CachedAnnotation>.ValueCollection.Enumerator annotationsEnumerator = _currentAnnotations.Values.GetEnumerator();
 
            while (annotationsEnumerator.MoveNext())
            {
                Annotation annotation = annotationsEnumerator.Current.Annotation;
                bool matchesQuery = false;
 
                foreach (AnnotationResource resource in annotation.Anchors)
                {
                    foreach (ContentLocatorBase locator in resource.ContentLocators)
                    {
                        ContentLocator ContentLocator = locator as ContentLocator;
                        if (ContentLocator != null)
                        {
                            if (ContentLocator.StartsWith(anchorLocator))
                            {
                                matchesQuery = true;
                            }
                        }
                        else
                        {
                            ContentLocatorGroup ContentLocatorGroup = locator as ContentLocatorGroup;
                            if (ContentLocatorGroup != null)
                            {
                                foreach (ContentLocator list in ContentLocatorGroup.Locators)
                                {
                                    if (list.StartsWith(anchorLocator))
                                    {
                                        matchesQuery = true;
                                        break;
                                    }
                                }
                            }
                        }
 
                        if (matchesQuery)
                        {
                            annotations.Add(annotation.Id, annotation);
                            break;
                        }
                    }
 
                    if (matchesQuery)
                        break;
                }
            }
 
            return annotations;
        }
 
        /// <summary>
        /// Returns a dictionary of all annotations in the map
        /// </summary>
        /// <returns>annotations list. Can return an empty list, but never null.</returns>
        public Dictionary<Guid, Annotation> FindAnnotations()
        {
            Dictionary<Guid, Annotation> annotations = new Dictionary<Guid, Annotation>();
            foreach (KeyValuePair<Guid, CachedAnnotation> annotKV in _currentAnnotations)
            {
                annotations.Add(annotKV.Key, annotKV.Value.Annotation);
            }
            return annotations;
        }
 
        /// <summary>
        /// Find an annottaion that has a specific id in the map
        /// returns null if not found
        /// </summary>
        /// <param name="id">id of the annotation to find</param>
        public Annotation FindAnnotation(Guid id)
        {
            CachedAnnotation cachedAnnotation = null;
            if (_currentAnnotations.TryGetValue(id, out cachedAnnotation))
            {
                return cachedAnnotation.Annotation;
            }
 
            return null;
        }
 
        /// <summary>
        /// Returns a list of all dirty annotations in the map
        /// </summary>
        /// <returns>annotations list. Can return an empty list, but never null.</returns>
        public List<Annotation> FindDirtyAnnotations()
        {
            List<Annotation> annotations = new List<Annotation>();
            foreach (KeyValuePair<Guid, CachedAnnotation> annotKV in _currentAnnotations)
            {
                if (annotKV.Value.Dirty)
                {
                    annotations.Add(annotKV.Value.Annotation);
                }
            }
 
            return annotations;
        }
 
        /// <summary>
        /// Sets all the dirty annotations in the map to clean
        /// </summary>
        public void ValidateDirtyAnnotations()
        {
            foreach (KeyValuePair<Guid, CachedAnnotation> annotKV in _currentAnnotations)
            {
                if (annotKV.Value.Dirty)
                {
                    annotKV.Value.Dirty = false;
                }
            }
        }
 
        #endregion Public Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// the event handler for AnchorChanged Annotation event
        /// internally it calls the store AnnotationModified delegate
        /// </summary>
        /// <param name="sender">annotation that changed</param>
        /// <param name="args">args describing the change</param>
        private void OnAnchorChanged(object sender, AnnotationResourceChangedEventArgs args)
        {
            _currentAnnotations[args.Annotation.Id].Dirty = true;
            _anchorChanged(sender, args);
        }
 
        /// <summary>
        /// the event handler for CargoChanged Annotation event
        /// internally it calls the store AnnotationModified delegate
        /// </summary>
        /// <param name="sender">annotation that changed</param>
        /// <param name="args">args describing the change</param>
        private void OnCargoChanged(object sender, AnnotationResourceChangedEventArgs args)
        {
            _currentAnnotations[args.Annotation.Id].Dirty = true;
            _cargoChanged(sender, args);
        }
 
        /// <summary>
        /// the event handler for AuthorChanged Annotation event
        /// internally it calls the store AnnotationModified delegate
        /// </summary>
        /// <param name="sender">annotation that changed</param>
        /// <param name="args">args describing the change</param>
        private void OnAuthorChanged(object sender, AnnotationAuthorChangedEventArgs args)
        {
            _currentAnnotations[args.Annotation.Id].Dirty = true;
            _authorChanged(sender, args);
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // a dictionary that holds an annotation id to an annotation instance
        private Dictionary<Guid, CachedAnnotation> _currentAnnotations = new Dictionary<Guid, CachedAnnotation>();
 
        // The delegates to be notified for Annotation changes
        private AnnotationAuthorChangedEventHandler _authorChanged;
        private AnnotationResourceChangedEventHandler _anchorChanged;
        private AnnotationResourceChangedEventHandler _cargoChanged;
 
        #endregion Private Fields
 
        //------------------------------------------------------
        //
        //  Private Classes
        //
        //------------------------------------------------------
 
        #region Private Classes
 
        /// <summary>
        /// This class contains an annotation and a dirty flag 
        /// It is needed because when it is time to flush
        /// the store - we should write only dirty annotations
        /// </summary>
        private class CachedAnnotation
        {
            /// <summary>
            /// Construct a CachedAnnotation
            /// </summary>
            /// <param name="annotation">The annotation instance to be cached</param>
            /// <param name="dirty">A flag to indicate if the annotation is dirty</param>
            public CachedAnnotation(Annotation annotation, bool dirty)
            {
                Annotation = annotation;
                Dirty = dirty;
            }
 
            /// <summary>
            /// The cached Annotation instance
            /// </summary>
            /// <value></value>
            public Annotation Annotation
            {
                get { return _annotation; }
                set { _annotation = value; }
            }
 
            /// <summary>
            /// A flag to indicate if the cached annotation is dirty or not
            /// </summary>
            /// <value></value>
            public bool Dirty
            {
                get { return _dirty; }
                set { _dirty = value; }
            }
 
 
            private Annotation _annotation;
            private bool _dirty;
        }
 
        #endregion Private Classes
    }
}