New tests.
[mono.git] / mcs / class / System.ComponentModel.Composition / src / ComponentModel / System / ComponentModel / Composition / Hosting / ComposablePartExportProvider.cs
1 // -----------------------------------------------------------------------\r
2 // Copyright (c) Microsoft Corporation.  All rights reserved.\r
3 // -----------------------------------------------------------------------\r
4 using System;\r
5 using System.Collections.Generic;\r
6 using System.ComponentModel.Composition.Primitives;\r
7 using System.Diagnostics;\r
8 using System.Globalization;\r
9 using System.Linq;\r
10 using System.Threading;\r
11 using Microsoft.Internal;\r
12 \r
13 namespace System.ComponentModel.Composition.Hosting\r
14 {\r
15     public class ComposablePartExportProvider : ExportProvider, IDisposable\r
16     {\r
17         private List<ComposablePart> _parts = new List<ComposablePart>();\r
18         private volatile bool _isDisposed = false;\r
19         private volatile bool _isRunning = false;\r
20         private CompositionLock _lock = null;\r
21         private ExportProvider _sourceProvider;\r
22         private ImportEngine _importEngine;\r
23         private volatile bool _currentlyComposing;\r
24 \r
25         /// <summary>\r
26         /// Initializes a new instance of the <see cref="ComposablePartExportProvider"/> class.\r
27         /// </summary>\r
28         public ComposablePartExportProvider() : \r
29             this(false)\r
30         {\r
31         }\r
32 \r
33         public ComposablePartExportProvider(bool isThreadSafe)\r
34         {\r
35             this._lock = new CompositionLock(isThreadSafe);\r
36         }\r
37 \r
38         /// <summary>\r
39         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\r
40         /// </summary>\r
41         public void Dispose()\r
42         {\r
43             this.Dispose(true);\r
44             GC.SuppressFinalize(this);\r
45         }\r
46 \r
47         /// <summary>\r
48         /// Releases unmanaged and - optionally - managed resources\r
49         /// </summary>\r
50         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>\r
51         protected virtual void Dispose(bool disposing)\r
52         {\r
53             if (disposing)\r
54             {\r
55                 if (!this._isDisposed)\r
56                 {\r
57                     bool disposeLock = false;\r
58                     ImportEngine oldImportEngine = null;\r
59                     try\r
60                     {\r
61                         using (this._lock.LockStateForWrite())\r
62                         {\r
63                             if (!this._isDisposed)\r
64                             {\r
65                                 oldImportEngine = this._importEngine;\r
66                                 this._importEngine = null;\r
67                                 this._sourceProvider = null;\r
68                                 this._isDisposed = true;\r
69                                 disposeLock = true;\r
70                             }\r
71                         }\r
72                     }\r
73                     finally\r
74                     {\r
75                         if (oldImportEngine != null)\r
76                         {\r
77                             oldImportEngine.Dispose();\r
78                         }\r
79 \r
80                         if (disposeLock)\r
81                         {\r
82                             this._lock.Dispose();\r
83                         }\r
84                     }\r
85                 }\r
86             }\r
87         }\r
88 \r
89         /// <summary>\r
90         ///     Gets the export provider which provides the provider access to\r
91         ///     exports.\r
92         /// </summary>\r
93         /// <value>\r
94         ///     The <see cref="ExportProvider"/> which provides the \r
95         ///     <see cref="ComposablePartExportProvider"/> access to <see cref="Export"/> objects. \r
96         ///     The default is <see langword="null"/>.\r
97         /// </value>\r
98         /// <exception cref="ArgumentNullException">\r
99         ///     <paramref name="value"/> is <see langword="null"/>.\r
100         /// </exception>\r
101         /// <exception cref="InvalidOperationException">\r
102         ///     This property has already been set.\r
103         ///     <para>\r
104         ///         -or-\r
105         ///     </para>\r
106         ///     The methods on the <see cref="ComposablePartExportProvider"/> \r
107         ///     have already been accessed.\r
108         /// </exception>\r
109         /// <exception cref="ObjectDisposedException">\r
110         ///     The <see cref="ComposablePartExportProvider"/> has been disposed of.\r
111         /// </exception>\r
112         /// <remarks>\r
113         ///     This property must be set before accessing any methods on the \r
114         ///     <see cref="ComposablePartExportProvider"/>.\r
115         /// </remarks>\r
116         public ExportProvider SourceProvider\r
117         {\r
118             get\r
119             {\r
120                 this.ThrowIfDisposed();\r
121 \r
122                 return this._sourceProvider;\r
123             }\r
124             set\r
125             {\r
126                 this.ThrowIfDisposed();\r
127 \r
128                 Requires.NotNull(value, "value");\r
129                 using (this._lock.LockStateForWrite())\r
130                 {\r
131                     this.EnsureCanSet(this._sourceProvider);\r
132                     this._sourceProvider = value;\r
133                 }\r
134 \r
135                 // This should be safe to do outside the lock, because only the first setter will ever win\r
136                 // and others will throw\r
137                 ImportEngine importEngine = new ImportEngine(this._sourceProvider, this._lock.IsThreadSafe);\r
138                 Thread.MemoryBarrier();\r
139                 this._importEngine = importEngine;\r
140             }\r
141         }\r
142 \r
143         /// <summary>\r
144         /// Returns all exports that match the conditions of the specified import.\r
145         /// </summary>\r
146         /// <param name="definition">The <see cref="ImportDefinition"/> that defines the conditions of the\r
147         /// <see cref="Export"/> to get.</param>\r
148         /// <returns></returns>\r
149         /// <result>\r
150         /// An <see cref="IEnumerable{T}"/> of <see cref="Export"/> objects that match\r
151         /// the conditions defined by <see cref="ImportDefinition"/>, if found; otherwise, an\r
152         /// empty <see cref="IEnumerable{T}"/>.\r
153         /// </result>\r
154         /// <remarks>\r
155         ///     <note type="inheritinfo">\r
156         /// The implementers should not treat the cardinality-related mismatches as errors, and are not\r
157         /// expected to throw exceptions in those cases.\r
158         /// For instance, if the import requests exactly one export and the provider has no matching exports or more than one,\r
159         /// it should return an empty <see cref="IEnumerable{T}"/> of <see cref="Export"/>.\r
160         /// </note>\r
161         /// </remarks>\r
162         protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)\r
163         {\r
164             this.ThrowIfDisposed();\r
165             this.EnsureRunning();\r
166 \r
167             // Determine whether there is a composition atomicComposition-specific list of parts to use,\r
168             // failing that use the usual list.  We never change the list of parts in place,\r
169             // but rather copy, change and write a new list atomically.  Therefore all we need\r
170             // to do here is to read the _parts member.\r
171             List<ComposablePart> parts = null;\r
172             using (this._lock.LockStateForRead())\r
173             {\r
174                 parts = atomicComposition.GetValueAllowNull(this, this._parts);\r
175             }\r
176 \r
177             if (parts.Count == 0)\r
178             {\r
179                 return Enumerable.Empty<Export>();\r
180             }\r
181 \r
182             List<Export> exports = new List<Export>();\r
183             foreach (var part in parts)\r
184             {\r
185                 foreach (var exportDefinition in part.ExportDefinitions)\r
186                 {\r
187                     if (definition.IsConstraintSatisfiedBy(exportDefinition))\r
188                     {\r
189                         exports.Add(this.CreateExport(part, exportDefinition));\r
190                     }\r
191                 }\r
192             }\r
193             return exports;\r
194         }    \r
195 \r
196         public void Compose(CompositionBatch batch)\r
197         {\r
198             this.ThrowIfDisposed();\r
199             this.EnsureRunning();\r
200 \r
201             Requires.NotNull(batch, "batch");\r
202 \r
203             // Quick exit test can be done prior to cloning since it's just an optimization, not a\r
204             // change in behavior\r
205             if ((batch.PartsToAdd.Count == 0) && (batch.PartsToRemove.Count == 0))\r
206             {\r
207                 return;\r
208             }\r
209 \r
210             CompositionResult result = CompositionResult.SucceededResult;\r
211 \r
212             // Clone the batch, so that the external changes wouldn't happen half-way thorugh compose\r
213             // NOTE : this does not guarantee the atomicity of cloning, which is not the goal anyway, \r
214             // rather the fact that all subsequent calls will deal with an unchanging batch\r
215             batch = new CompositionBatch(batch.PartsToAdd, batch.PartsToRemove);\r
216 \r
217             var newParts = GetUpdatedPartsList(batch);\r
218 \r
219             // Allow only recursive calls from the import engine to see the changes until\r
220             // they've been verified ...\r
221             using (var atomicComposition = new AtomicComposition())\r
222             {\r
223                 // Don't allow reentrant calls to compose during previewing to prevent\r
224                 // corrupted state.\r
225                 if (this._currentlyComposing)\r
226                 {\r
227                     throw new InvalidOperationException(Strings.ReentrantCompose);\r
228                 }\r
229 \r
230                 this._currentlyComposing = true;\r
231 \r
232                 try\r
233                 {\r
234                     // In the meantime recursive calls need to be able to see the list as well\r
235                     atomicComposition.SetValue(this, newParts);\r
236 \r
237                     // Recompose any existing imports effected by the these changes first so that\r
238                     // adapters, resurrected parts, etc. can all play their role in satisfying\r
239                     // imports for added parts\r
240                     this.Recompose(batch, atomicComposition);\r
241 \r
242                     // Ensure that required imports can be satisfied\r
243                     foreach (ComposablePart part in batch.PartsToAdd)\r
244                     {\r
245                         // collect the result of previewing all the adds in the batch\r
246                         try\r
247                         {\r
248                             this._importEngine.PreviewImports(part, atomicComposition);\r
249                         }\r
250                         catch (ChangeRejectedException ex)\r
251                         {\r
252                             result = result.MergeResult(new CompositionResult(ex.Errors));\r
253                         }\r
254                     }\r
255 \r
256                     result.ThrowOnErrors(atomicComposition);\r
257 \r
258                     // Complete the new parts since they passed previewing.`\r
259                     using (this._lock.LockStateForWrite())\r
260                     {\r
261                         this._parts = newParts;\r
262                     }\r
263 \r
264                     atomicComposition.Complete();\r
265                 }\r
266                 finally\r
267                 {\r
268                     this._currentlyComposing = false;\r
269                 }\r
270             }\r
271 \r
272             // Satisfy Imports\r
273             // - Satisfy imports on all newly added component parts\r
274             foreach (ComposablePart part in batch.PartsToAdd)\r
275             {\r
276                 result = result.MergeResult(CompositionServices.TryInvoke(() =>\r
277                     this._importEngine.SatisfyImports(part)));\r
278             }\r
279 \r
280             // return errors\r
281             result.ThrowOnErrors();\r
282         }\r
283 \r
284         private List<ComposablePart> GetUpdatedPartsList(CompositionBatch batch)\r
285         {\r
286             Assumes.NotNull(batch);\r
287 \r
288             // Copy the current list of parts - we are about to modify it\r
289             // This is an OK thing to do as this is the only method that can modify the List AND Compose can\r
290             // only be executed on one thread at a time - thus two different threads cannot tramp over each other\r
291             List<ComposablePart> parts = null;\r
292             using (this._lock.LockStateForRead())\r
293             {\r
294                 parts = this._parts.ToList(); // this copies the list\r
295             }\r
296 \r
297             foreach (ComposablePart part in batch.PartsToAdd)\r
298             {\r
299                 parts.Add(part);\r
300             }\r
301 \r
302             foreach (ComposablePart part in batch.PartsToRemove)\r
303             {\r
304                 parts.Remove(part);\r
305             }\r
306 \r
307             return parts;\r
308         }\r
309 \r
310         private void Recompose(CompositionBatch batch, AtomicComposition atomicComposition)\r
311         {\r
312             Assumes.NotNull(batch);\r
313 \r
314             // Unregister any removed component parts\r
315             foreach (ComposablePart part in batch.PartsToRemove)\r
316             {\r
317                 this._importEngine.ReleaseImports(part, atomicComposition);\r
318             }\r
319 \r
320             // Recompose any imports effected by the these changes (the changes are\r
321             // observable through GetExports in the appropriate atomicComposition, thus we can fire\r
322             // the event\r
323             IEnumerable<ExportDefinition> addedExports = batch.PartsToAdd.Count != 0 ?\r
324                 batch.PartsToAdd.SelectMany(part => part.ExportDefinitions).ToArray() :\r
325                 new ExportDefinition[0];\r
326 \r
327             IEnumerable<ExportDefinition> removedExports = batch.PartsToRemove.Count != 0 ?\r
328                 batch.PartsToRemove.SelectMany(part => part.ExportDefinitions).ToArray() :\r
329                 new ExportDefinition[0];\r
330 \r
331             this.OnExportsChanging(\r
332                 new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition));\r
333 \r
334             atomicComposition.AddCompleteAction(() => this.OnExportsChanged(\r
335                 new ExportsChangeEventArgs(addedExports, removedExports, null)));\r
336         }\r
337 \r
338         private Export CreateExport(ComposablePart part, ExportDefinition export)\r
339         {\r
340             return new Export(export, () => GetExportedValue(part, export));\r
341         }\r
342 \r
343         private object GetExportedValue(ComposablePart part, ExportDefinition export)\r
344         {\r
345             this.ThrowIfDisposed();\r
346             this.EnsureRunning();\r
347 \r
348             return CompositionServices.GetExportedValueFromComposedPart(this._importEngine, part, export);\r
349         }\r
350 \r
351         [DebuggerStepThrough]\r
352         private void ThrowIfDisposed()\r
353         {\r
354             if (this._isDisposed)\r
355             {\r
356                 throw new ObjectDisposedException(this.GetType().Name);\r
357             }\r
358         }\r
359 \r
360         [DebuggerStepThrough]\r
361         private void EnsureCanRun()\r
362         {\r
363             if ((this._sourceProvider == null) || (this._importEngine == null))\r
364             {\r
365                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectMustBeInitialized, "SourceProvider")); // NOLOC\r
366             }\r
367         }\r
368 \r
369         [DebuggerStepThrough]\r
370         private void EnsureRunning()\r
371         {\r
372             if (!this._isRunning)\r
373             {\r
374                 using (this._lock.LockStateForWrite())\r
375                 {\r
376                     if (!this._isRunning)\r
377                     {\r
378                         this.EnsureCanRun();\r
379                         this._isRunning = true;\r
380                     }\r
381                 }\r
382             }\r
383         }\r
384 \r
385         [DebuggerStepThrough]\r
386         private void EnsureCanSet<T>(T currentValue)\r
387             where T : class\r
388         {\r
389             if ((this._isRunning) || (currentValue != null))\r
390             {\r
391                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectAlreadyInitialized));\r
392             }\r
393         }\r
394     }\r
395 }\r