diff --git a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs index 70ea3f52f..14381e75e 100644 --- a/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs +++ b/src/Orchard.Tests/Environment/Extensions/ExtensionManagerTests.cs @@ -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(" a + b.Id + "<"), Is.EqualTo(" a + b.Id + "<"), Is.EqualTo(" a + b.Id + "<"), Is.EqualTo(" a + b.Id + "<"), Is.EqualTo(" a + b.Id + "<"), Is.EqualTo(" a + b.Id + "<"), Is.EqualTo(" 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); } diff --git a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs index 0c6360197..b6ea1504f 100644 --- a/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionLoaderCoordinator.cs @@ -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 { diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs index 4b02ddd40..a6636bfc3 100644 --- a/src/Orchard/Environment/Extensions/ExtensionManager.cs +++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs @@ -39,7 +39,11 @@ namespace Orchard.Environment.Extensions { public IEnumerable 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; } /// diff --git a/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs b/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs index 2392b0c6f..9afe6d7bf 100644 --- a/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs +++ b/src/Orchard/Environment/Extensions/Folders/ExtensionFolders.cs @@ -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; diff --git a/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs b/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs index ef048cefa..d264de22f 100644 --- a/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs +++ b/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs @@ -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 Features { get; set; } } diff --git a/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs b/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs index acd9e002a..d24046b35 100644 --- a/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs +++ b/src/Orchard/Environment/Extensions/Models/FeatureDescriptor.cs @@ -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 Dependencies { get; set; } } } \ No newline at end of file diff --git a/src/Orchard/Environment/ShellBuilders/Models/ShellBlueprint.cs b/src/Orchard/Environment/ShellBuilders/Models/ShellBlueprint.cs index 2f49e08dd..978ff23f2 100644 --- a/src/Orchard/Environment/ShellBuilders/Models/ShellBlueprint.cs +++ b/src/Orchard/Environment/ShellBuilders/Models/ShellBlueprint.cs @@ -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 { diff --git a/src/Orchard/Utility/DependencyOrderingUtility.cs b/src/Orchard/Utility/DependencyOrderingUtility.cs index cf02d7e6e..757d113c6 100644 --- a/src/Orchard/Utility/DependencyOrderingUtility.cs +++ b/src/Orchard/Utility/DependencyOrderingUtility.cs @@ -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. /// - public static IEnumerable OrderByDependencies(this IEnumerable elements, Func hasDependency) { + public static IEnumerable OrderByDependenciesAndPriorities(this IEnumerable elements, Func hasDependency, Func getPriority) { var population = elements.Select(d => new Linkage { Element = d - }).ToArray(); + }).OrderBy(item => getPriority(item.Element)).ToArray(); // Performing an initial sorting by priorities may optimize performance var result = new List(); 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(Linkage item, ICollection list, IEnumerable> population, Func hasDependency) { + private static void Add(Linkage item, ICollection list, IEnumerable> population, Func hasDependency, Func 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(List list, int index, Func hasDependency, Func 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; + } + + /// + /// Advances an element within the list from an initial position to a final position with a lower index. + /// + /// The type of each element. + /// the list of elements. + /// The initial position within the list. + /// The final position within the list. + /// True if any change was made; false otherwise. + private static bool SwitchAndShift(List 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; + } } } \ No newline at end of file