PageRenderTime 62ms CodeModel.GetById 31ms app.highlight 26ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.ComponentModel.Composition/src/ComponentModel/System/ComponentModel/Composition/Hosting/AtomicComposition.cs

https://github.com/iainlane/mono
C# | 305 lines | 225 code | 39 blank | 41 comment | 44 complexity | 079f29fb825d91ee99011c4179a7676f MD5 | raw file
  1using System;
  2using System.Diagnostics;
  3using System.Collections.Generic;
  4using Microsoft.Internal;
  5using System.Diagnostics.CodeAnalysis;
  6
  7namespace System.ComponentModel.Composition.Hosting
  8{
  9    /// <summary>
 10    /// AtomicComposition provides lightweight atomicCompositional semantics to enable temporary
 11    /// state to be managed for a series of nested atomicCompositions.  Each atomicComposition maintains
 12    /// queryable state along with a sequence of actions necessary to complete the state when
 13    /// the atomicComposition is no longer in danger of being rolled back.  State is completed or
 14    /// rolled back when the atomicComposition is disposed, depending on the state of the
 15    /// CompleteOnDipose property which defaults to false.  The using(...) pattern in C# is a
 16    /// convenient mechanism for defining atomicComposition scopes.
 17    /// 
 18    /// The least obvious aspects of AtomicComposition deal with nesting.
 19    /// 
 20    /// Firstly, no complete actions are actually performed until the outermost atomicComposition is
 21    /// completed.  Completeting or rolling back nested atomicCompositions serves only to change which
 22    /// actions would be completed the outer atomicComposition.
 23    /// 
 24    /// Secondly, state is added in the form of queries associated with an object key.  The
 25    /// key represents a unique object the state is being held on behalf of.  The quieries are
 26    /// accessed throught the Query methods which provide automatic chaining to execute queries
 27    /// across the target atomicComposition and its inner atomicComposition as appropriate.
 28    /// 
 29    /// Lastly, when a nested atomicComposition is created for a given outer the outer atomicComposition is locked.
 30    /// It remains locked until the inner atomicComposition is disposed or completeed preventing the addition of
 31    /// state, actions or other inner atomicCompositions.
 32    /// </summary>
 33    public class AtomicComposition : IDisposable
 34    {
 35        private readonly AtomicComposition _outerAtomicComposition;
 36        private KeyValuePair<object, object>[] _values;
 37        private int _valueCount = 0;
 38        private List<Action> _completeActionList;
 39        private List<Action> _revertActionList;
 40        private bool _isDisposed = false;
 41        private bool _isCompleted = false;
 42        private bool _containsInnerAtomicComposition = false;
 43
 44        public AtomicComposition()
 45            : this(null)
 46        {
 47        }
 48
 49        public AtomicComposition(AtomicComposition outerAtomicComposition)
 50        {
 51            // Lock the inner atomicComposition so that we can assume nothing changes except on
 52            // the innermost scope, and thereby optimize the query path
 53            if (outerAtomicComposition != null)
 54            {
 55                this._outerAtomicComposition = outerAtomicComposition;
 56                this._outerAtomicComposition.ContainsInnerAtomicComposition = true;
 57            }
 58        }
 59
 60        public void SetValue(object key, object value)
 61        {
 62            ThrowIfDisposed();
 63            ThrowIfCompleteed();
 64            ThrowIfContainsInnerAtomicComposition();
 65
 66            Requires.NotNull(key, "key");
 67
 68            SetValueInternal(key, value);
 69        }
 70
 71        public bool TryGetValue<T>(object key, out T value) 
 72        {
 73            return TryGetValue(key, false, out value);
 74        }
 75
 76        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
 77        public bool TryGetValue<T>(object key, bool localAtomicCompositionOnly, out T value) 
 78        {
 79            ThrowIfDisposed();
 80            ThrowIfCompleteed();
 81
 82            Requires.NotNull(key, "key");
 83
 84            return TryGetValueInternal(key, localAtomicCompositionOnly, out value);
 85        }
 86
 87        public void AddCompleteAction(Action completeAction)
 88        {
 89            ThrowIfDisposed();
 90            ThrowIfCompleteed();
 91            ThrowIfContainsInnerAtomicComposition();
 92
 93            Requires.NotNull(completeAction, "completeAction");
 94
 95            if (this._completeActionList == null)
 96            {
 97                this._completeActionList = new List<Action>();
 98            }
 99            this._completeActionList.Add(completeAction);
100        }
101
102        public void AddRevertAction(Action revertAction)
103        {
104            ThrowIfDisposed();
105            ThrowIfCompleteed();
106            ThrowIfContainsInnerAtomicComposition();
107
108            Requires.NotNull(revertAction, "revertAction");
109
110            if (this._revertActionList == null)
111            {
112                this._revertActionList = new List<Action>();
113            }
114            this._revertActionList.Add(revertAction);
115        }
116
117        public void Complete()
118        {
119            ThrowIfDisposed();
120            ThrowIfCompleteed();
121
122            if (this._outerAtomicComposition == null)
123            {   // Execute all the complete actions
124                FinalComplete();
125            }
126            else
127            {   // Copy the actions and state to the outer atomicComposition
128                CopyComplete();
129            }
130
131            this._isCompleted = true;
132        }
133
134        public void Dispose()
135        {
136            Dispose(true);
137            GC.SuppressFinalize(this);
138        }
139
140        protected virtual void Dispose(bool disposing)
141        {
142            ThrowIfDisposed();
143            this._isDisposed = true;
144
145            if (this._outerAtomicComposition != null)
146            {
147                this._outerAtomicComposition.ContainsInnerAtomicComposition = false;
148            }
149
150            // Revert is always immediate and involves forgetting information and
151            // exceuting any appropriate revert actions
152            if (!this._isCompleted)
153            {
154                if (this._revertActionList != null)
155                {
156                    // Execute the revert actions in reverse order to ensure
157                    // everything incrementally rollsback its state.
158                    for (int i = this._revertActionList.Count - 1; i >= 0; i--)
159                    {
160                        Action action = this._revertActionList[i];
161                        action();
162                    }
163                    this._revertActionList = null;
164                }
165            }
166        }
167
168        private void FinalComplete()
169        {
170            // Completeting the outer most scope is easy, just execute all the actions
171            if (this._completeActionList != null)
172            {
173                foreach (Action action in this._completeActionList)
174                {
175                    action();
176                }
177                this._completeActionList = null;
178            }
179        }
180
181        private void CopyComplete()
182        {
183            Assumes.NotNull(this._outerAtomicComposition);
184
185            this._outerAtomicComposition.ContainsInnerAtomicComposition = false;
186
187            // Inner scopes are much odder, because completeting them means coalescing them into the
188            // outer scope - the complete or revert actions are deferred until the outermost scope completes
189            // or any intermediate rolls back
190            if (this._completeActionList != null)
191            {
192                foreach (Action action in this._completeActionList)
193                {
194                    this._outerAtomicComposition.AddCompleteAction(action);
195                }
196            }
197
198            if (this._revertActionList != null)
199            {
200                foreach (Action action in this._revertActionList)
201                {
202                    this._outerAtomicComposition.AddRevertAction(action);
203                }
204            }
205
206            // We can copy over existing atomicComposition entries because they're either already chained or
207            // overwrite by design and can now be completed or rolled back together
208            for (var index = 0; index < this._valueCount; index++)
209            {
210                this._outerAtomicComposition.SetValueInternal(
211                    this._values[index].Key, this._values[index].Value);
212            }
213        }
214
215        private bool ContainsInnerAtomicComposition
216        {
217            set
218            {
219                if (value == true && this._containsInnerAtomicComposition == true)
220                {
221                    throw new InvalidOperationException(Strings.AtomicComposition_AlreadyNested);
222                }
223                this._containsInnerAtomicComposition = value;
224            }
225        }
226
227        private bool TryGetValueInternal<T>(object key, bool localAtomicCompositionOnly, out T value) 
228        {
229            for (var index = 0; index < this._valueCount; index++)
230            {
231                if (this._values[index].Key == key)
232                {
233                    value = (T)this._values[index].Value;
234                    return true;
235                }
236            }
237
238            // If there's no atomicComposition available then recurse until we hit the outermost
239            // scope, where upon we go ahead and return null
240            if (!localAtomicCompositionOnly && this._outerAtomicComposition != null)
241            {
242                return this._outerAtomicComposition.TryGetValueInternal<T>(key, localAtomicCompositionOnly, out value);
243            }
244
245            value = default(T);
246            return false;
247        }
248
249        private void SetValueInternal(object key, object value)
250        {
251            // Handle overwrites quickly
252            for (var index = 0; index < this._valueCount; index++)
253            {
254                if (this._values[index].Key == key)
255                {
256                    this._values[index] = new KeyValuePair<object,object>(key, value);
257                    return;
258                }
259            }
260
261            // Expand storage when needed
262            if (this._values == null || this._valueCount == this._values.Length)
263            {
264                var newQueries = new KeyValuePair<object, object>[this._valueCount == 0 ? 5 : this._valueCount * 2];
265                if (this._values != null)
266                {
267                    Array.Copy(this._values, newQueries, this._valueCount);
268                }
269                this._values = newQueries;
270            }
271
272            // Store a new entry
273            this._values[_valueCount] = new KeyValuePair<object, object>(key, value);
274            this._valueCount++;
275            return;
276        }
277
278        [DebuggerStepThrough]
279        private void ThrowIfContainsInnerAtomicComposition()
280        {
281            if (this._containsInnerAtomicComposition)
282            {
283                throw new InvalidOperationException(Strings.AtomicComposition_PartOfAnotherAtomicComposition);
284            }
285        }
286
287        [DebuggerStepThrough]
288        private void ThrowIfCompleteed()
289        {
290            if (this._isCompleted)
291            {
292                throw new InvalidOperationException(Strings.AtomicComposition_AlreadyCompleted);
293            }
294        }
295
296        [DebuggerStepThrough]
297        private void ThrowIfDisposed()
298        {
299            if (this._isDisposed)
300            {
301                throw ExceptionBuilder.CreateObjectDisposed(this);
302            }
303        }
304    }
305}