Implementing feature priorities

--HG--
branch : dev
This commit is contained in:
Andre Rodrigues
2011-03-20 17:16:41 -07:00
parent f4fadb2eee
commit b67a348267
9 changed files with 208 additions and 19 deletions

View File

@@ -506,9 +506,7 @@ OrchardVersion: 1
Name: Alpha
Version: 1.0.3
OrchardVersion: 1
Features:
Alpha:
Dependencies: Gamma
Dependencies: Gamma
");
moduleExtensionFolder.Manifests.Add("Beta", @"
@@ -520,23 +518,139 @@ OrchardVersion: 1
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Features:
Gamma:
Dependencies: Beta
Dependencies: Beta
");
moduleExtensionFolder.Manifests.Add("Classic", @"
Name: Classic
Version: 1.0.3
OrchardVersion: 1
Features:
Classic:
Dependencies: Alpha
Dependencies: Alpha
");
IExtensionManager extensionManager = new ExtensionManager(new[] { moduleExtensionFolder, themeExtensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Classic<"));
}
[Test]
public void FeatureDescriptorsAreInDependencyAndPriorityOrder() {
var extensionLoader = new StubLoaders();
var extensionFolder = new StubFolders();
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
extensionFolder.Manifests.Add("Alpha", @"
Name: Alpha
Version: 1.0.3
OrchardVersion: 1
Dependencies: Gamma
Priority:2"); // More important than Gamma but will get overwritten by the dependency
extensionFolder.Manifests.Add("Beta", @"
Name: Beta
Version: 1.0.3
OrchardVersion: 1
Priority:2");
extensionFolder.Manifests.Add("Foo", @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1");
extensionFolder.Manifests.Add("Gamma", @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Dependencies: Beta, Foo
Priority:3");
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Beta<Gamma<Alpha<"));
// Change priorities and see that it reflects properly
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:3";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
// Remove dependency on Beta and see that it moves down the list since no one depends on it anymore
extensionFolder.Manifests["Gamma"] = @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Dependencies: Beta
Priority:3";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Foo<Gamma<Alpha<"));
// Change Foo to depend on Gamma and see that it moves to a new position (same dependencies as alpha but lower priority)
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:3
Dependencies: Gamma";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Alpha<Foo<"));
// Update Foo to a higher priority than alpha
extensionFolder.Manifests["Foo"] = @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1
Dependencies: Gamma";
extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Beta<Gamma<Foo<Alpha<"));
}
[Test]
public void FeatureDescriptorsAreInPriorityOrder() {
var extensionLoader = new StubLoaders();
var extensionFolder = new StubFolders();
// Check that priorities apply correctly on items on the same level of dependencies and are overwritten by dependencies
extensionFolder.Manifests.Add("Alpha", @"
Name: Alpha
Version: 1.0.3
OrchardVersion: 1
Priority:4"); // More important than Gamma but will get overwritten by the dependency
extensionFolder.Manifests.Add("Beta", @"
Name: Beta
Version: 1.0.3
OrchardVersion: 1
Priority:3");
extensionFolder.Manifests.Add("Foo", @"
Name: Foo
Version: 1.0.3
OrchardVersion: 1
Priority:1");
extensionFolder.Manifests.Add("Gamma", @"
Name: Gamma
Version: 1.0.3
OrchardVersion: 1
Priority:2");
IExtensionManager extensionManager = new ExtensionManager(new[] { extensionFolder }, new[] { extensionLoader }, new StubCacheManager());
var features = extensionManager.AvailableFeatures();
Assert.That(features.Aggregate("<", (a, b) => a + b.Id + "<"), Is.EqualTo("<Foo<Gamma<Beta<Alpha<"));
}
}
}

View File

@@ -37,7 +37,7 @@ namespace Orchard.DisplayManagement.Descriptors {
var alterations = builderFactory.BuildAlterations()
.Where(alteration => IsModuleOrRequestedTheme(alteration, themeName))
.OrderByDependencies(AlterationHasDependency);
.OrderByDependenciesAndPriorities(AlterationHasDependency, GetPriority);
var descriptors = alterations.GroupBy(alteration => alteration.ShapeType, StringComparer.OrdinalIgnoreCase)
.Select(group => group.Aggregate(
@@ -54,6 +54,10 @@ namespace Orchard.DisplayManagement.Descriptors {
});
}
private static int GetPriority(ShapeAlteration shapeAlteration) {
return shapeAlteration.Feature.Descriptor.Priority;
}
private static bool AlterationHasDependency(ShapeAlteration item, ShapeAlteration subject) {
return ExtensionManager.HasDependency(item.Feature.Descriptor, subject.Feature.Descriptor);
}

View File

@@ -199,9 +199,10 @@ namespace Orchard.Environment.Extensions {
.ToDictionary(g => g.Key, g => g.AsEnumerable(), StringComparer.OrdinalIgnoreCase);
var sortedAvailableExtensions =
availableExtensions.OrderByDependencies(
availableExtensions.OrderByDependenciesAndPriorities(
(item, dep) => referencesByModule.ContainsKey(item.Id) &&
referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name)))
referencesByModule[item.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, r.Name)),
(item) => item.Priority)
.ToList();
return new ExtensionLoadingContext {

View File

@@ -39,7 +39,11 @@ namespace Orchard.Environment.Extensions {
public IEnumerable<FeatureDescriptor> AvailableFeatures() {
return _cacheManager.Get("...", ctx =>
AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependencies(HasDependency).ToReadOnlyCollection());
AvailableExtensions().SelectMany(ext => ext.Features).OrderByDependenciesAndPriorities(HasDependency, GetPriority).ToReadOnlyCollection());
}
internal static int GetPriority(FeatureDescriptor featureDescriptor) {
return featureDescriptor.Priority;
}
/// <summary>

View File

@@ -100,6 +100,7 @@ namespace Orchard.Environment.Extensions.Folders {
AntiForgery = GetValue(manifest, "AntiForgery"),
Zones = GetValue(manifest, "Zones"),
BaseTheme = GetValue(manifest, "BaseTheme"),
Priority = int.Parse(GetValue(manifest, "Priority") ?? "0")
};
extensionDescriptor.Features = GetFeaturesForExtension(manifest, extensionDescriptor);
@@ -183,6 +184,9 @@ namespace Orchard.Environment.Extensions.Folders {
case "FeatureDescription":
manifest.Add("FeatureDescription", field[1]);
break;
case "Priority":
manifest.Add("Priority", field[1]);
break;
case "Features":
manifest.Add("Features", reader.ReadToEnd());
break;
@@ -200,6 +204,7 @@ namespace Orchard.Environment.Extensions.Folders {
FeatureDescriptor defaultFeature = new FeatureDescriptor {
Id = extensionDescriptor.Id,
Name = extensionDescriptor.Name,
Priority = extensionDescriptor.Priority,
Description = GetValue(manifest, "FeatureDescription") ?? GetValue(manifest, "Description") ?? string.Empty,
Dependencies = ParseFeatureDependenciesEntry(GetValue(manifest, "Dependencies")),
Extension = extensionDescriptor,
@@ -229,6 +234,7 @@ namespace Orchard.Environment.Extensions.Folders {
if (featureDescriptorId == extensionDescriptor.Id) {
featureDescriptor = defaultFeature;
featureDescriptor.Name = extensionDescriptor.Name;
featureDescriptor.Priority = extensionDescriptor.Priority;
}
else {
featureDescriptor = new FeatureDescriptor {
@@ -256,6 +262,9 @@ namespace Orchard.Environment.Extensions.Folders {
case "Category":
featureDescriptor.Category = featureField[1];
break;
case "Priority":
featureDescriptor.Priority = int.Parse(featureField[1]);
break;
case "Dependencies":
featureDescriptor.Dependencies = ParseFeatureDependenciesEntry(featureField[1]);
break;

View File

@@ -29,6 +29,7 @@ namespace Orchard.Environment.Extensions.Models {
public string AntiForgery { get; set; }
public string Zones { get; set; }
public string BaseTheme { get; set; }
public int Priority { get; set; }
public IEnumerable<FeatureDescriptor> Features { get; set; }
}

View File

@@ -13,6 +13,7 @@ namespace Orchard.Environment.Extensions.Models {
public string Name { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public int Priority { get; set; }
public IEnumerable<string> Dependencies { get; set; }
}
}

View File

@@ -22,7 +22,7 @@ namespace Orchard.Environment.ShellBuilders.Models {
public class ShellBlueprintItem {
public Type Type { get; set; }
public Feature Feature { get; set; }
public Feature Feature { get; set; }
}
public class DependencyBlueprint : ShellBlueprintItem {

View File

@@ -15,27 +15,82 @@ namespace Orchard.Utility {
/// is a dependency of another, this algorithm will return the collection of elements sorted
/// so that a given element in the sequence doesn't depend on any other element further in the sequence.
/// </summary>
public static IEnumerable<T> OrderByDependencies<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency) {
public static IEnumerable<T> OrderByDependenciesAndPriorities<T>(this IEnumerable<T> elements, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
var population = elements.Select(d => new Linkage<T> {
Element = d
}).ToArray();
}).OrderBy(item => getPriority(item.Element)).ToArray(); // Performing an initial sorting by priorities may optimize performance
var result = new List<T>();
foreach (var item in population) {
Add(item, result, population, hasDependency);
Add(item, result, population, hasDependency, getPriority);
}
// shift elements forward as possible within priorities and dependencies
for (int i = 1; i < result.Count; i++) {
int bestPosition = BestPriorityPosition(result, i, hasDependency, getPriority);
SwitchAndShift(result, i, bestPosition);
}
return result;
}
private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency) {
private static void Add<T>(Linkage<T> item, ICollection<T> list, IEnumerable<Linkage<T>> population, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
if (item.Used)
return;
item.Used = true;
foreach (var dependency in population.Where(dep => hasDependency(item.Element, dep.Element))) {
Add(dependency, list, population, hasDependency);
Add(dependency, list, population, hasDependency, getPriority);
}
list.Add(item.Element);
}
private static int BestPriorityPosition<T>(List<T> list, int index, Func<T, T, bool> hasDependency, Func<T, int> getPriority) {
int bestPriority = getPriority(list[index]);
int bestIndex = index;
for (int i = index - 1 ; i >= 0 ; i--) {
if (hasDependency(list[index], list[i])) {
return bestIndex;
}
int currentPriority = getPriority(list[i]);
if (currentPriority >= bestPriority) {
bestIndex = i;
}
}
return bestIndex;
}
/// <summary>
/// Advances an element within the list from an initial position to a final position with a lower index.
/// </summary>
/// <typeparam name="T">The type of each element.</typeparam>
/// <param name="list">the list of elements.</param>
/// <param name="initialPosition">The initial position within the list.</param>
/// <param name="finalPosition">The final position within the list.</param>
/// <returns>True if any change was made; false otherwise.</returns>
private static bool SwitchAndShift<T>(List<T> list, int initialPosition, int finalPosition) {
if (initialPosition < finalPosition) {
throw new ArgumentException("finalPosition");
}
if (initialPosition != finalPosition) {
T temp = list[initialPosition];
for (; initialPosition > finalPosition; initialPosition--) {
list[initialPosition] = list[initialPosition - 1];
}
list[finalPosition] = temp;
return true;
}
return false;
}
}
}