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