From c53aa123772a84909fbe92ceff9033adb27af640 Mon Sep 17 00:00:00 2001 From: dfsek Date: Thu, 18 Nov 2021 21:13:33 -0700 Subject: [PATCH] addon dependency sorting --- common/api/addons/build.gradle.kts | 1 + .../com/dfsek/terra/api/addon/BaseAddon.java | 12 +++ .../com/dfsek/terra/api/addon/TerraAddon.java | 7 +- .../com/dfsek/terra/AbstractPlatform.java | 26 ++++--- .../java/com/dfsek/terra/InternalAddon.java | 9 +++ .../dfsek/terra/addon/DependencySorter.java | 73 +++++++++++++++++++ .../CircularDependencyException.java | 11 +++ .../addon/dependency/DependencyException.java | 13 ++++ .../DependencyVersionException.java | 11 +++ .../terra/addon/BootstrapAddonLoader.java | 10 +++ .../com/dfsek/terra/fabric/FabricAddon.java | 9 ++- 11 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 common/implementation/src/main/java/com/dfsek/terra/addon/DependencySorter.java create mode 100644 common/implementation/src/main/java/com/dfsek/terra/addon/dependency/CircularDependencyException.java create mode 100644 common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyException.java create mode 100644 common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyVersionException.java diff --git a/common/api/addons/build.gradle.kts b/common/api/addons/build.gradle.kts index 4936c5616..7c41547a4 100644 --- a/common/api/addons/build.gradle.kts +++ b/common/api/addons/build.gradle.kts @@ -1,3 +1,4 @@ dependencies { "shadedApi"(project(":common:api:util")) + "shadedApi"("ca.solo-studios:strata:1.0.0") } diff --git a/common/api/addons/src/main/java/com/dfsek/terra/api/addon/BaseAddon.java b/common/api/addons/src/main/java/com/dfsek/terra/api/addon/BaseAddon.java index 3a131be7b..cf29931f6 100644 --- a/common/api/addons/src/main/java/com/dfsek/terra/api/addon/BaseAddon.java +++ b/common/api/addons/src/main/java/com/dfsek/terra/api/addon/BaseAddon.java @@ -1,8 +1,20 @@ package com.dfsek.terra.api.addon; +import ca.solostudios.strata.version.Version; +import ca.solostudios.strata.version.VersionRange; + import com.dfsek.terra.api.util.StringIdentifiable; +import java.util.Collections; +import java.util.Map; + public interface BaseAddon extends StringIdentifiable { default void initialize() { } + + default Map getDependencies() { + return Collections.emptyMap(); + } + + Version getVersion(); } diff --git a/common/api/core/src/main/java/com/dfsek/terra/api/addon/TerraAddon.java b/common/api/core/src/main/java/com/dfsek/terra/api/addon/TerraAddon.java index 0cb3e5ba9..92fe2a7a3 100644 --- a/common/api/core/src/main/java/com/dfsek/terra/api/addon/TerraAddon.java +++ b/common/api/core/src/main/java/com/dfsek/terra/api/addon/TerraAddon.java @@ -1,6 +1,7 @@ package com.dfsek.terra.api.addon; +import ca.solostudios.strata.Versions; import org.jetbrains.annotations.NotNull; import com.dfsek.terra.api.addon.annotations.Addon; @@ -11,6 +12,8 @@ import com.dfsek.terra.api.addon.annotations.Version; /** * Represents an entry point for an com.dfsek.terra.addon. Implementations must be annotated with {@link Addon}. */ + +//todo delete this public abstract class TerraAddon implements BaseAddon { /** * Invoked immediately after an com.dfsek.terra.addon is loaded. @@ -22,9 +25,9 @@ public abstract class TerraAddon implements BaseAddon { * * @return Addon version. */ - public final @NotNull String getVersion() { + public final @NotNull ca.solostudios.strata.version.Version getVersion() { Version version = getClass().getAnnotation(Version.class); - return version == null ? "0.1.0" : version.value(); + return Versions.getVersion(1, 2, 3); } /** diff --git a/common/implementation/src/main/java/com/dfsek/terra/AbstractPlatform.java b/common/implementation/src/main/java/com/dfsek/terra/AbstractPlatform.java index 8fb9c6810..9497e3d45 100644 --- a/common/implementation/src/main/java/com/dfsek/terra/AbstractPlatform.java +++ b/common/implementation/src/main/java/com/dfsek/terra/AbstractPlatform.java @@ -13,11 +13,13 @@ import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import com.dfsek.terra.addon.BootstrapAddonLoader; +import com.dfsek.terra.addon.DependencySorter; import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.addon.BaseAddon; import com.dfsek.terra.api.command.CommandManager; @@ -137,8 +139,6 @@ public abstract class AbstractPlatform implements Platform { logger().info("Initializing Terra..."); - getPlatformAddon().ifPresent(addon -> addonRegistry.register(addon.getID(), addon)); - try(InputStream stream = getClass().getResourceAsStream("/config.yml")) { File configFile = new File(getDataFolder(), "config.yml"); if(!configFile.exists()) { @@ -192,13 +192,17 @@ public abstract class AbstractPlatform implements Platform { profiler.start(); } + List addonList = new ArrayList<>(); + InternalAddon internalAddon = new InternalAddon(); - addonRegistry.register(internalAddon.getID(), internalAddon); + addonList.add(internalAddon); + + getPlatformAddon().ifPresent(addonList::add); platformAddon().ifPresent(baseAddon -> { baseAddon.initialize(); - addonRegistry.register(baseAddon.getID(), baseAddon); + addonList.add(baseAddon); }); BootstrapAddonLoader bootstrapAddonLoader = new BootstrapAddonLoader(this); @@ -212,13 +216,17 @@ public abstract class AbstractPlatform implements Platform { .forEach(bootstrap -> { platformInjector.inject(bootstrap); bootstrap.loadAddons(addonsFolder, getClass().getClassLoader()) - .forEach(addon -> { - platformInjector.inject(addon); - addon.initialize(); - addonRegistry.register(addon.getID(), addon); - }); + .forEach(addonList::add); }); + DependencySorter sorter = new DependencySorter(); + addonList.forEach(sorter::add); + sorter.sort().forEach(addon -> { + platformInjector.inject(addon); + addon.initialize(); + addonRegistry.register(addon.getID(), addon); + }); + eventManager .getHandler(FunctionalEventHandler.class) .register(internalAddon, PlatformInitializationEvent.class) diff --git a/common/implementation/src/main/java/com/dfsek/terra/InternalAddon.java b/common/implementation/src/main/java/com/dfsek/terra/InternalAddon.java index 6e6ee04d0..3b8e9567d 100644 --- a/common/implementation/src/main/java/com/dfsek/terra/InternalAddon.java +++ b/common/implementation/src/main/java/com/dfsek/terra/InternalAddon.java @@ -1,11 +1,20 @@ package com.dfsek.terra; +import ca.solostudios.strata.Versions; +import ca.solostudios.strata.version.Version; + import com.dfsek.terra.api.addon.BaseAddon; public class InternalAddon implements BaseAddon { + private static final Version VERSION = Versions.getVersion(1, 0, 0); @Override public String getID() { return "terra"; } + + @Override + public Version getVersion() { + return VERSION; + } } diff --git a/common/implementation/src/main/java/com/dfsek/terra/addon/DependencySorter.java b/common/implementation/src/main/java/com/dfsek/terra/addon/DependencySorter.java new file mode 100644 index 000000000..b343e41f8 --- /dev/null +++ b/common/implementation/src/main/java/com/dfsek/terra/addon/DependencySorter.java @@ -0,0 +1,73 @@ +package com.dfsek.terra.addon; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.dfsek.terra.addon.dependency.CircularDependencyException; +import com.dfsek.terra.addon.dependency.DependencyException; +import com.dfsek.terra.addon.dependency.DependencyVersionException; +import com.dfsek.terra.api.addon.BaseAddon; + + +public class DependencySorter { + private final Map addons = new HashMap<>(); + private final Map visited = new HashMap<>(); + + private final List addonList = new ArrayList<>(); + + public void add(BaseAddon addon) { + addons.put(addon.getID(), addon); + visited.put(addon, false); + addonList.add(addon); + } + + private void sortDependencies(BaseAddon addon, List sort) { + addon.getDependencies().forEach((id, range) -> { + if(!addons.containsKey(id)) { + throw new DependencyException("Addon " + addon.getID() + " specifies dependency on " + id + ", versions " + range + ", but no such addon is installed."); + } + + BaseAddon dependency = addons.get(id); + + if(!range.isSatisfiedBy(dependency.getVersion())) { + throw new DependencyVersionException("Addon " + addon.getID() + " specifies dependency on " + id + ", versions " + range + ", but non-matching version " + dependency.getVersion() + " is installed.."); + } + + if(!visited.get(dependency)) { // if we've not visited it yet + visited.put(dependency, true); // we've visited it now + + if(!dependency.getDependencies().isEmpty()) { // if this addon has dependencies... + sortDependencies(dependency, sort); // sort them first. + } + + if(sort.contains(dependency)) { + throw new CircularDependencyException("Addon " + addon.getID() + " has circular dependency beginning with " + dependency.getID()); + } + + sort.add(dependency); // add it to the list. + } + }); + } + + public List sort() { + List sorted = new ArrayList<>(); + + for(int i = addonList.size() - 1; i >= 0; i--) { + BaseAddon addon = addonList.get(i); + addonList.remove(i); + + if(!visited.get(addon)) { // if we've not visited it yet + visited.put(addon, true); // we've visited it now + if(!addon.getDependencies().isEmpty()) { // if this addon has dependencies... + sortDependencies(addon, sorted); + } + } + + sorted.add(addon); + } + + return sorted; + } +} diff --git a/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/CircularDependencyException.java b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/CircularDependencyException.java new file mode 100644 index 000000000..65cd138d7 --- /dev/null +++ b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/CircularDependencyException.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addon.dependency; + +public class CircularDependencyException extends DependencyException{ + public CircularDependencyException(String message) { + super(message); + } + + public CircularDependencyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyException.java b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyException.java new file mode 100644 index 000000000..7b1f1034b --- /dev/null +++ b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyException.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.addon.dependency; + + + +public class DependencyException extends RuntimeException { + public DependencyException(String message) { + super(message); + } + + public DependencyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyVersionException.java b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyVersionException.java new file mode 100644 index 000000000..0ea0f8c88 --- /dev/null +++ b/common/implementation/src/main/java/com/dfsek/terra/addon/dependency/DependencyVersionException.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addon.dependency; + +public class DependencyVersionException extends DependencyException{ + public DependencyVersionException(String message) { + super(message); + } + + public DependencyVersionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/loader/addon/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java b/common/loader/addon/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java index 35af2be96..97f4e2d85 100644 --- a/common/loader/addon/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java +++ b/common/loader/addon/src/main/java/com/dfsek/terra/addon/BootstrapAddonLoader.java @@ -9,6 +9,9 @@ import java.nio.file.Path; import java.util.jar.JarFile; import java.util.stream.Collectors; +import ca.solostudios.strata.Versions; +import ca.solostudios.strata.version.Version; + import com.dfsek.terra.addon.exception.AddonLoadException; import com.dfsek.terra.api.Platform; import com.dfsek.terra.api.addon.bootstrap.BootstrapBaseAddon; @@ -17,6 +20,8 @@ import com.dfsek.terra.api.addon.bootstrap.BootstrapBaseAddon; public class BootstrapAddonLoader implements BootstrapBaseAddon> { private final Platform platform; + private static final Version VERSION = Versions.getVersion(1, 0, 0); + public BootstrapAddonLoader(Platform platform) { this.platform = platform; } @Override @@ -66,4 +71,9 @@ public class BootstrapAddonLoader implements BootstrapBaseAddon> templates = new HashMap<>(); @@ -107,6 +109,11 @@ public final class FabricAddon implements BaseAddon { .global(); } + @Override + public Version getVersion() { + return VERSION; + } + private void injectTree(CheckedRegistry registry, String id, ConfiguredFeature tree) { try {