addon dependency sorting

This commit is contained in:
dfsek
2021-11-18 21:13:33 -07:00
parent ceeb59301e
commit c53aa12377
11 changed files with 170 additions and 12 deletions

View File

@@ -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<BaseAddon> 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)

View File

@@ -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;
}
}

View File

@@ -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<String, BaseAddon> addons = new HashMap<>();
private final Map<BaseAddon, Boolean> visited = new HashMap<>();
private final List<BaseAddon> 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<BaseAddon> 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<BaseAddon> sort() {
List<BaseAddon> 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}