| File: MS\Internal\Annotations\Storage\StoreAnnotationsMap.cs | Web Access |
| Project: src\wpf\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. // // 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 } }