/Phone/TinyMEF/System/ComponentModel/Composition/Hosting/AtomicComposition.cs

# · C# · 342 lines · 253 code · 45 blank · 44 comment · 46 complexity · 18ace7dfaed1cfc9cb2dcf6b7fb29e46 MD5 · raw file

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