/mcs/class/System.ComponentModel.Composition/src/ComponentModel/System/ComponentModel/Composition/Hosting/AtomicComposition.cs
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}