1 // -----------------------------------------------------------------------
\r
2 // Copyright (c) Microsoft Corporation. All rights reserved.
\r
3 // -----------------------------------------------------------------------
\r
5 using System.Collections.Generic;
\r
6 using System.ComponentModel.Composition.Primitives;
\r
7 using System.Diagnostics;
\r
8 using System.Globalization;
\r
10 using System.Threading;
\r
11 using Microsoft.Internal;
\r
13 namespace System.ComponentModel.Composition.Hosting
\r
15 public class ComposablePartExportProvider : ExportProvider, IDisposable
\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
26 /// Initializes a new instance of the <see cref="ComposablePartExportProvider"/> class.
\r
28 public ComposablePartExportProvider() :
\r
33 public ComposablePartExportProvider(bool isThreadSafe)
\r
35 this._lock = new CompositionLock(isThreadSafe);
\r
39 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
\r
41 public void Dispose()
\r
44 GC.SuppressFinalize(this);
\r
48 /// Releases unmanaged and - optionally - managed resources
\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
55 if (!this._isDisposed)
\r
57 bool disposeLock = false;
\r
58 ImportEngine oldImportEngine = null;
\r
61 using (this._lock.LockStateForWrite())
\r
63 if (!this._isDisposed)
\r
65 oldImportEngine = this._importEngine;
\r
66 this._importEngine = null;
\r
67 this._sourceProvider = null;
\r
68 this._isDisposed = true;
\r
75 if (oldImportEngine != null)
\r
77 oldImportEngine.Dispose();
\r
82 this._lock.Dispose();
\r
90 /// Gets the export provider which provides the provider access to
\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
98 /// <exception cref="ArgumentNullException">
\r
99 /// <paramref name="value"/> is <see langword="null"/>.
\r
101 /// <exception cref="InvalidOperationException">
\r
102 /// This property has already been set.
\r
106 /// The methods on the <see cref="ComposablePartExportProvider"/>
\r
107 /// have already been accessed.
\r
109 /// <exception cref="ObjectDisposedException">
\r
110 /// The <see cref="ComposablePartExportProvider"/> has been disposed of.
\r
113 /// This property must be set before accessing any methods on the
\r
114 /// <see cref="ComposablePartExportProvider"/>.
\r
116 public ExportProvider SourceProvider
\r
120 this.ThrowIfDisposed();
\r
122 return this._sourceProvider;
\r
126 this.ThrowIfDisposed();
\r
128 Requires.NotNull(value, "value");
\r
129 using (this._lock.LockStateForWrite())
\r
131 this.EnsureCanSet(this._sourceProvider);
\r
132 this._sourceProvider = value;
\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
144 /// Returns all exports that match the conditions of the specified import.
\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
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
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
162 protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
\r
164 this.ThrowIfDisposed();
\r
165 this.EnsureRunning();
\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
174 parts = atomicComposition.GetValueAllowNull(this, this._parts);
\r
177 if (parts.Count == 0)
\r
179 return Enumerable.Empty<Export>();
\r
182 List<Export> exports = new List<Export>();
\r
183 foreach (var part in parts)
\r
185 foreach (var exportDefinition in part.ExportDefinitions)
\r
187 if (definition.IsConstraintSatisfiedBy(exportDefinition))
\r
189 exports.Add(this.CreateExport(part, exportDefinition));
\r
196 public void Compose(CompositionBatch batch)
\r
198 this.ThrowIfDisposed();
\r
199 this.EnsureRunning();
\r
201 Requires.NotNull(batch, "batch");
\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
210 CompositionResult result = CompositionResult.SucceededResult;
\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
217 var newParts = GetUpdatedPartsList(batch);
\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
223 // Don't allow reentrant calls to compose during previewing to prevent
\r
224 // corrupted state.
\r
225 if (this._currentlyComposing)
\r
227 throw new InvalidOperationException(Strings.ReentrantCompose);
\r
230 this._currentlyComposing = true;
\r
234 // In the meantime recursive calls need to be able to see the list as well
\r
235 atomicComposition.SetValue(this, newParts);
\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
242 // Ensure that required imports can be satisfied
\r
243 foreach (ComposablePart part in batch.PartsToAdd)
\r
245 // collect the result of previewing all the adds in the batch
\r
248 this._importEngine.PreviewImports(part, atomicComposition);
\r
250 catch (ChangeRejectedException ex)
\r
252 result = result.MergeResult(new CompositionResult(ex.Errors));
\r
256 result.ThrowOnErrors(atomicComposition);
\r
258 // Complete the new parts since they passed previewing.`
\r
259 using (this._lock.LockStateForWrite())
\r
261 this._parts = newParts;
\r
264 atomicComposition.Complete();
\r
268 this._currentlyComposing = false;
\r
273 // - Satisfy imports on all newly added component parts
\r
274 foreach (ComposablePart part in batch.PartsToAdd)
\r
276 result = result.MergeResult(CompositionServices.TryInvoke(() =>
\r
277 this._importEngine.SatisfyImports(part)));
\r
281 result.ThrowOnErrors();
\r
284 private List<ComposablePart> GetUpdatedPartsList(CompositionBatch batch)
\r
286 Assumes.NotNull(batch);
\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
294 parts = this._parts.ToList(); // this copies the list
\r
297 foreach (ComposablePart part in batch.PartsToAdd)
\r
302 foreach (ComposablePart part in batch.PartsToRemove)
\r
304 parts.Remove(part);
\r
310 private void Recompose(CompositionBatch batch, AtomicComposition atomicComposition)
\r
312 Assumes.NotNull(batch);
\r
314 // Unregister any removed component parts
\r
315 foreach (ComposablePart part in batch.PartsToRemove)
\r
317 this._importEngine.ReleaseImports(part, atomicComposition);
\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
323 IEnumerable<ExportDefinition> addedExports = batch.PartsToAdd.Count != 0 ?
\r
324 batch.PartsToAdd.SelectMany(part => part.ExportDefinitions).ToArray() :
\r
325 new ExportDefinition[0];
\r
327 IEnumerable<ExportDefinition> removedExports = batch.PartsToRemove.Count != 0 ?
\r
328 batch.PartsToRemove.SelectMany(part => part.ExportDefinitions).ToArray() :
\r
329 new ExportDefinition[0];
\r
331 this.OnExportsChanging(
\r
332 new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition));
\r
334 atomicComposition.AddCompleteAction(() => this.OnExportsChanged(
\r
335 new ExportsChangeEventArgs(addedExports, removedExports, null)));
\r
338 private Export CreateExport(ComposablePart part, ExportDefinition export)
\r
340 return new Export(export, () => GetExportedValue(part, export));
\r
343 private object GetExportedValue(ComposablePart part, ExportDefinition export)
\r
345 this.ThrowIfDisposed();
\r
346 this.EnsureRunning();
\r
348 return CompositionServices.GetExportedValueFromComposedPart(this._importEngine, part, export);
\r
351 [DebuggerStepThrough]
\r
352 private void ThrowIfDisposed()
\r
354 if (this._isDisposed)
\r
356 throw new ObjectDisposedException(this.GetType().Name);
\r
360 [DebuggerStepThrough]
\r
361 private void EnsureCanRun()
\r
363 if ((this._sourceProvider == null) || (this._importEngine == null))
\r
365 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectMustBeInitialized, "SourceProvider")); // NOLOC
\r
369 [DebuggerStepThrough]
\r
370 private void EnsureRunning()
\r
372 if (!this._isRunning)
\r
374 using (this._lock.LockStateForWrite())
\r
376 if (!this._isRunning)
\r
378 this.EnsureCanRun();
\r
379 this._isRunning = true;
\r
385 [DebuggerStepThrough]
\r
386 private void EnsureCanSet<T>(T currentValue)
\r
389 if ((this._isRunning) || (currentValue != null))
\r
391 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.ObjectAlreadyInitialized));
\r