Skip to content

Commit 324ed41

Browse files
committed
feat: override export with the same uri
1 parent d7bf2ed commit 324ed41

3 files changed

Lines changed: 118 additions & 16 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.ComponentModel;
2+
3+
namespace TagBites.ComponentModel.Composition;
4+
5+
[PublicAPI]
6+
[EditorBrowsable(EditorBrowsableState.Never)]
7+
[AttributeUsage(AttributeTargets.Assembly)]
8+
public class AssemblyExportSettingsAttribute : Attribute
9+
{
10+
public ExportDuplicateUriHandling DuplicateUriHandling { get; set; }
11+
}

src/TagBites.ComponentModel.Composition/ComponentModel/Composition/ExportComponentManager.cs

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
using System;
2-
using System.Collections.Generic;
31
using System.Collections.ObjectModel;
42
using System.ComponentModel;
53
using System.ComponentModel.Composition;
6-
using System.IO;
7-
using System.Linq;
84
using System.Reflection;
9-
using JetBrains.Annotations;
105
#if NETCOREAPP
116
using System.Runtime.Loader;
127
#endif
@@ -32,6 +27,7 @@ public class ExportComponentManager
3227

3328
private readonly object _locker = new();
3429
private readonly HashSet<Assembly> _loadedAssemblies = new();
30+
private readonly List<(Assembly Assembly, List<IExportData> Removed)> _removedExports = new();
3531
private readonly Dictionary<Uri, IExportData> _exports = new();
3632
private readonly MultiDoubleDictionary<Type, string, List<IExportData>, IExportData> _exportTree = new();
3733

@@ -102,6 +98,9 @@ public ExportComponent GetExport(Uri location)
10298
if (data == null)
10399
return null;
104100

101+
while (data.OverrideBy != null)
102+
data = data.OverrideBy;
103+
105104
return data.Component;
106105
}
107106
}
@@ -322,6 +321,7 @@ public void LoadAssembly(Type typeInRequestedAssembly)
322321
public void LoadAssembly(Assembly assembly)
323322
{
324323
var changedContractTypes = new HashSet<Type>();
324+
var duplicateUriHandling = assembly.GetCustomAttribute<AssemblyExportSettingsAttribute>()?.DuplicateUriHandling ?? ExportDuplicateUriHandling.SkipCurrent;
325325

326326
lock (_locker)
327327
{
@@ -390,19 +390,45 @@ public void LoadAssembly(Assembly assembly)
390390
// Apply changes
391391
lock (_locker)
392392
{
393+
List<IExportData> removed = null;
394+
393395
foreach (var definition in items)
394396
{
395-
if (_exports.ContainsKey(definition.Location))
397+
if (_exports.TryGetValue(definition.Location, out var existing) && duplicateUriHandling == ExportDuplicateUriHandling.SkipCurrent)
396398
continue;
397399

398400
var data = new ExportData(definition);
399401

400-
_exports.Add(definition.Location, data);
402+
if (existing == null)
403+
_exports.Add(definition.Location, data);
404+
else if (duplicateUriHandling == ExportDuplicateUriHandling.OverrideExisting)
405+
{
406+
while (existing.OverrideBy != null)
407+
existing = existing.OverrideBy;
408+
409+
existing.OverrideBy = data;
410+
}
411+
else
412+
{
413+
removed ??= new List<IExportData>(2);
414+
removed.Add(existing);
415+
416+
UnregisterCore(existing.Component, true);
417+
418+
while (_exports.TryGetValue(definition.Location, out existing))
419+
UnregisterCore(existing.Component, true);
420+
421+
_exports.Add(definition.Location, data);
422+
}
423+
401424
_exportTree.Add(definition.ContractType, definition.ContractName ?? string.Empty, data);
402425

403426
changedContractTypes.Add(definition.ContractType);
404427
}
405428

429+
if (removed != null)
430+
_removedExports.Add((assembly, removed));
431+
406432
if (loadDirectly)
407433
_loadedAssemblyOutsideOfCache = true;
408434
}
@@ -428,19 +454,56 @@ public void UnloadAssembly(Assembly assembly)
428454
foreach (var collection in _exportTree.Values)
429455
{
430456
for (var i = collection.Count - 1; i >= 0; i--)
431-
if (collection[i].OriginAssembly == assembly)
457+
{
458+
var item = collection[i];
459+
if (item.OriginAssembly == assembly)
432460
{
433-
changedContractTypes.Add(collection[i].Definition.ContractType);
434-
_exports.Remove(collection[i].Definition.Location);
461+
changedContractTypes.Add(item.Definition.ContractType);
435462
collection.RemoveAt(i);
463+
464+
RemoveLocation(item);
436465
}
466+
}
437467
}
468+
469+
var removed = _removedExports.FirstOrDefault(x => x.Assembly == assembly).Removed;
470+
if (removed != null)
471+
foreach (var data in removed)
472+
{
473+
if (_loadedAssemblies.Contains(data.OriginAssembly))
474+
{
475+
Register(data.Component, true, true);
476+
changedContractTypes.Add(data.Definition.ContractType);
477+
}
478+
}
438479
}
439480

440481
RaiseExportCollectionChanged(changedContractTypes.ToArray());
441482
}
483+
private void RemoveLocation(IExportData item)
484+
{
485+
var location = item.Definition.Location;
486+
if (_exports.TryGetValue(location, out var data))
487+
if (data == item)
488+
{
489+
if (data.OverrideBy != null)
490+
_exports[location] = data.OverrideBy;
491+
else
492+
_exports.Remove(location);
493+
}
494+
else
495+
{
496+
for (; data.OverrideBy != null; data = data.OverrideBy)
497+
if (data.OverrideBy == item)
498+
{
499+
data.OverrideBy = data.OverrideBy.OverrideBy;
500+
break;
501+
}
502+
}
503+
}
442504

443-
public void Register<T>(ExportComponent<T> component)
505+
public void Register<T>(ExportComponent<T> component) => Register((ExportComponent)component);
506+
private void Register(ExportComponent component, bool skipExisting = false, bool skipEvent = false)
444507
{
445508
if (component == null)
446509
throw new ArgumentNullException(nameof(component));
@@ -451,13 +514,19 @@ public void Register<T>(ExportComponent<T> component)
451514
{
452515
var data = new RegisteredExportData(component);
453516
if (_exports.ContainsKey(component.Location))
517+
{
518+
if (skipExisting)
519+
return;
520+
454521
throw new Exception(string.Format("Component with the same url ({0}) is already registered.", component.Location));
522+
}
455523

456524
_exports.Add(component.Location, data);
457525
_exportTree.Add(component.ContractType, component.ContractName ?? string.Empty, data);
458526
}
459527

460-
RaiseExportCollectionChanged(new[] { component.ContractType });
528+
if (skipEvent)
529+
RaiseExportCollectionChanged(new[] { component.ContractType });
461530
}
462531
public bool Unregister(Uri location)
463532
{
@@ -482,7 +551,7 @@ public bool Unregister(Uri location)
482551
}
483552
}
484553

485-
_exports.Remove(location);
554+
RemoveLocation(data);
486555
contractType = data.Definition.ContractType;
487556
}
488557
}
@@ -495,7 +564,8 @@ public bool Unregister(Uri location)
495564

496565
return false;
497566
}
498-
public bool Unregister(ExportComponent component)
567+
public bool Unregister(ExportComponent component) => UnregisterCore(component, false);
568+
public bool UnregisterCore(ExportComponent component, bool force)
499569
{
500570
if (component == null)
501571
throw new ArgumentNullException(nameof(component));
@@ -506,9 +576,9 @@ public bool Unregister(ExportComponent component)
506576
if (collection != null)
507577
{
508578
for (var i = collection.Count - 1; i >= 0; i--)
509-
if (collection[i].IsRegistered && collection[i].Component == component)
579+
if (collection[i].Component == component && (force || collection[i].IsRegistered))
510580
{
511-
_exports.Remove(collection[i].Definition.Location);
581+
RemoveLocation(collection[i]);
512582
collection.RemoveAt(i);
513583
return true;
514584
}
@@ -736,6 +806,8 @@ private interface IExportData
736806
ExportComponent Component { get; }
737807
Assembly OriginAssembly { get; }
738808
bool IsRegistered { get; }
809+
810+
IExportData OverrideBy { get; set; }
739811
}
740812
private class ExportData : IExportData
741813
{
@@ -758,6 +830,7 @@ public ExportComponent Component
758830
}
759831
public Assembly OriginAssembly => Definition.ValueTypeAssembly;
760832
public bool IsRegistered => false;
833+
public IExportData OverrideBy { get; set; }
761834

762835
public ExportData(ExportComponentDefinition definition)
763836
{
@@ -770,6 +843,7 @@ private class RegisteredExportData : IExportData
770843
public ExportComponent Component { get; }
771844
public Assembly OriginAssembly => Component.OriginAssembly;
772845
public bool IsRegistered => true;
846+
public IExportData OverrideBy { get; set; }
773847

774848
public RegisteredExportData(ExportComponent component)
775849
{
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace TagBites.ComponentModel.Composition;
2+
3+
public enum ExportDuplicateUriHandling
4+
{
5+
/// <summary>
6+
/// Skip current export.
7+
/// </summary>
8+
SkipCurrent,
9+
/// <summary>
10+
/// Override existing export.
11+
/// </summary>
12+
OverrideExisting,
13+
/// <summary>
14+
/// Remove existing export.
15+
/// </summary>
16+
RemoveExisting
17+
}

0 commit comments

Comments
 (0)