diff --git a/common/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/common/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index 5319e85e8..651d21d68 100644 --- a/common/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/common/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -153,11 +153,7 @@ public synchronized Map getOrLoadMaps(Predicate filter) t try { loadMap(entry.getKey(), entry.getValue()); } catch (ConfigurationException ex) { - Logger.global.logWarning(ex.getFormattedExplanation()); - Throwable cause = ex.getRootCause(); - if (cause != null) { - Logger.global.logError("Detailed error:", ex); - } + ex.printLog(Logger.global); } } return Collections.unmodifiableMap(maps); diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/Addon.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/Addon.java new file mode 100644 index 000000000..4235f6ce3 --- /dev/null +++ b/common/src/main/java/de/bluecolored/bluemap/common/addons/Addon.java @@ -0,0 +1,39 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.common.addons; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.nio.file.Path; + +@RequiredArgsConstructor +@Getter +public class Addon { + + private final AddonInfo addonInfo; + private final Path jarFile; + +} diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonInfo.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonInfo.java index 8ac0ffdf2..740c38057 100644 --- a/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonInfo.java +++ b/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonInfo.java @@ -24,13 +24,59 @@ */ package de.bluecolored.bluemap.common.addons; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import de.bluecolored.bluemap.common.config.ConfigurationException; import lombok.Getter; +import org.jetbrains.annotations.Nullable; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +@SuppressWarnings({"FieldMayBeFinal", "unused"}) @Getter public class AddonInfo { public static final String ADDON_INFO_FILE = "bluemap.addon.json"; + private static final Gson GSON = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES) + .create(); + private String id; private String entrypoint; + private Set dependencies = Set.of(); + private Set softDependencies = Set.of(); + + public static @Nullable AddonInfo load(Path addonJarFile) throws ConfigurationException { + try (FileSystem fileSystem = FileSystems.newFileSystem(addonJarFile, (ClassLoader) null)) { + for (Path root : fileSystem.getRootDirectories()) { + Path addonInfoFile = root.resolve(ADDON_INFO_FILE); + if (!Files.exists(addonInfoFile)) continue; + + try (Reader reader = Files.newBufferedReader(addonInfoFile, StandardCharsets.UTF_8)) { + AddonInfo addonInfo = GSON.fromJson(reader, AddonInfo.class); + + if (addonInfo.getId() == null) + throw new ConfigurationException("'id' is missing"); + + if (addonInfo.getEntrypoint() == null) + throw new ConfigurationException("'entrypoint' is missing"); + + return addonInfo; + } + } + } catch (IOException e) { + throw new ConfigurationException("There was an exception trying to access the file.", e); + } + + return null; + } } diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonLoader.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonLoader.java new file mode 100644 index 000000000..c9dd0043b --- /dev/null +++ b/common/src/main/java/de/bluecolored/bluemap/common/addons/AddonLoader.java @@ -0,0 +1,194 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.common.addons; + +import de.bluecolored.bluemap.common.config.ConfigurationException; +import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.logger.Logger; +import lombok.Singular; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class AddonLoader { + public static final AddonLoader INSTANCE = new AddonLoader(); + + private final Map loadedAddons = new ConcurrentHashMap<>(); + + public void tryLoadAddons(Path root) { + if (!Files.exists(root)) return; + try (Stream files = Files.list(root)) { + + // find all addons and load addon-info + Map availableAddons = files + .filter(Files::isRegularFile) + .filter(f -> f.getFileName().toString().endsWith(".jar")) + .map(this::tryLoadAddonInfo) + .filter(Objects::nonNull) + .collect(Collectors.toMap(addon -> addon.getAddonInfo().getId(), addon -> addon)); + + // remove addons that have missing required dependencies + while (!availableAddons.isEmpty()) { + Addon addonToRemove = availableAddons.values().stream() + .filter(a -> !availableAddons.keySet().containsAll(a.getAddonInfo().getDependencies())) + .findAny() + .orElse(null); + if (addonToRemove == null) break; + String id = addonToRemove.getAddonInfo().getId(); + availableAddons.remove(id); + new ConfigurationException("Missing required dependencies %s to load addon '%s' (%s)".formatted( + Arrays.toString(addonToRemove.getAddonInfo().getDependencies().toArray(String[]::new)), + id, + addonToRemove.getJarFile() + )).printLog(Logger.global); + } + + // topography sort and load addons based on their dependencies + Map dependenciesToLoad = new HashMap<>(); + Queue loadNext = new ArrayDeque<>(); + for (Addon addon : availableAddons.values()) { + long dependencyCount = + addon.getAddonInfo().getDependencies().size() + + addon.getAddonInfo().getSoftDependencies().stream() + .filter(availableAddons::containsKey) + .count(); + String id = addon.getAddonInfo().getId(); + if (dependencyCount == 0) loadNext.add(id); + else dependenciesToLoad.put(id, dependencyCount); + } + + while (!loadNext.isEmpty()) { + String id = loadNext.poll(); + Addon addon = availableAddons.get(id); + + try { + loadAddon(addon); + for (Addon dependant : availableAddons.values()) { + AddonInfo info = dependant.getAddonInfo(); + if (info.getDependencies().contains(id) || info.getSoftDependencies().contains(id)) { + Long count = dependenciesToLoad.get(info.getId()); + if (count == null) continue; + if (--count <= 0) { + dependenciesToLoad.remove(info.getId()); + loadNext.add(info.getId()); + } else { + dependenciesToLoad.put(info.getId(), count); + } + } + } + } catch (ConfigurationException ex) { + new ConfigurationException("Failed to load addon '%s' (%s)".formatted(id, addon.getJarFile()), ex) + .printLog(Logger.global); + } + } + + // failed to resolve dependencies, possibly a cyclic reference + // try to load anyway in case a soft dependency is involved + for (String id : dependenciesToLoad.keySet()) { + Addon addon = availableAddons.remove(id); + try { + if (addon != null) loadAddon(addon); + } catch (ConfigurationException ex) { + new ConfigurationException("Failed to load addon '%s' (%s)".formatted(id, addon.getJarFile()), ex) + .printLog(Logger.global); + } + } + + } catch (IOException e) { + Logger.global.logError("Failed to load addons from '%s'".formatted(root), e); + } + } + + private @Nullable Addon tryLoadAddonInfo(Path jarFile) { + try { + AddonInfo addonInfo = AddonInfo.load(jarFile); + if (addonInfo == null) return null; + return new Addon(addonInfo, jarFile); + } catch (ConfigurationException e) { + new ConfigurationException("Failed to load addon info from '%s'.".formatted(jarFile), e) + .printLog(Logger.global); + return null; + } + } + + private synchronized void loadAddon(Addon addon) throws ConfigurationException { + AddonInfo addonInfo = addon.getAddonInfo(); + Path jarFile = addon.getJarFile(); + + Logger.global.logInfo("Loading BlueMap Addon: %s (%s)".formatted(addonInfo.getId(), jarFile)); + + try { + Set dependencyClassLoaders = new LinkedHashSet<>(); + for (String dependencyId : addon.getAddonInfo().getDependencies()) { + LoadedAddon loadedAddon = loadedAddons.get(dependencyId); + if (loadedAddon == null) throw new IllegalStateException("Required dependency '%s' is not loaded." + .formatted(addon.getAddonInfo().getId())); + dependencyClassLoaders.add(loadedAddon.getClassLoader()); + } + for (String dependencyId : addon.getAddonInfo().getSoftDependencies()) { + LoadedAddon loadedAddon = loadedAddons.get(dependencyId); + if (loadedAddon == null) continue; + dependencyClassLoaders.add(loadedAddon.getClassLoader()); + } + + ClassLoader parent = BlueMap.class.getClassLoader(); + if (!dependencyClassLoaders.isEmpty()) + parent = new CombinedClassLoader(parent, dependencyClassLoaders.toArray(ClassLoader[]::new)); + + ClassLoader addonClassLoader = new URLClassLoader( + new URL[]{ jarFile.toUri().toURL() }, + parent + ); + Class entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint()); + + // create addon instance + Object instance = entrypointClass.getConstructor().newInstance(); + LoadedAddon loadedAddon = new LoadedAddon( + addonInfo, + jarFile, + addonClassLoader, + instance + ); + + loadedAddons.put(addonInfo.getId(), loadedAddon); + + // run addon + if (instance instanceof Runnable runnable) + runnable.run(); + + } catch (Exception e) { + throw new ConfigurationException("There was an exception trying to initialize the addon!", e); + } + } + +} diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/Addons.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/Addons.java deleted file mode 100644 index 54e44490a..000000000 --- a/common/src/main/java/de/bluecolored/bluemap/common/addons/Addons.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.common.addons; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import de.bluecolored.bluemap.common.config.ConfigurationException; -import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.logger.Logger; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.io.Reader; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -import static de.bluecolored.bluemap.common.addons.AddonInfo.ADDON_INFO_FILE; - -public final class Addons { - - private static final String PLUGIN_YML = "plugin.yml"; - private static final String MODS_TOML = "META-INF/mods.toml"; - private static final String FABRIC_MOD_JSON = "fabric.mod.json"; - - private static final Gson GSON = new GsonBuilder().create(); - private static final Map LOADED_ADDONS = new ConcurrentHashMap<>(); - - private Addons() { - throw new UnsupportedOperationException("Utility class"); - } - - public static void tryLoadAddons(Path root) { - tryLoadAddons(root, false); - } - - public static void tryLoadAddons(Path root, boolean expectOnlyAddons) { - if (!Files.exists(root)) return; - try (Stream files = Files.list(root)) { - files - .filter(Files::isRegularFile) - .filter(f -> f.getFileName().toString().endsWith(".jar")) - .forEach(expectOnlyAddons ? Addons::tryLoadAddon : Addons::tryLoadJar); - } catch (IOException e) { - Logger.global.logError("Failed to load addons from '%s'".formatted(root), e); - } - } - - public static void tryLoadAddon(Path addonJarFile) { - try { - AddonInfo addonInfo = loadAddonInfo(addonJarFile); - if (addonInfo == null) throw createRichExceptionForFile(addonJarFile); - - if (LOADED_ADDONS.containsKey(addonInfo.getId())) return; - - loadAddon(addonJarFile, addonInfo); - } catch (ConfigurationException e) { - ConfigurationException e2 = new ConfigurationException("BlueMap failed to load the addon '%s'!".formatted(addonJarFile), e); - Logger.global.logWarning(e2.getFormattedExplanation()); - Logger.global.logError(e2); - } - } - - public static void tryLoadJar(Path addonJarFile) { - try { - AddonInfo addonInfo = loadAddonInfo(addonJarFile); - if (addonInfo == null) { - Logger.global.logDebug("No %s found in '%s', skipping...".formatted(ADDON_INFO_FILE, addonJarFile)); - return; - } - - if (LOADED_ADDONS.containsKey(addonInfo.getId())) return; - - loadAddon(addonJarFile, addonInfo); - } catch (ConfigurationException e) { - ConfigurationException e2 = new ConfigurationException("BlueMap failed to load the addon '%s'!".formatted(addonJarFile), e); - Logger.global.logWarning(e2.getFormattedExplanation()); - Logger.global.logError(e2); - } - } - - public synchronized static void loadAddon(Path jarFile, AddonInfo addonInfo) throws ConfigurationException { - Logger.global.logInfo("Loading BlueMap Addon: %s (%s)".formatted(addonInfo.getId(), jarFile)); - - if (LOADED_ADDONS.containsKey(addonInfo.getId())) - throw new ConfigurationException("There is already an addon with same id ('%s') loaded!" - .formatted(addonInfo.getId())); - - try { - ClassLoader addonClassLoader = BlueMap.class.getClassLoader(); - Class entrypointClass; - - // try to find entrypoint class and load jar with new classloader if needed - try { - entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint()); - } catch (ClassNotFoundException e) { - addonClassLoader = new URLClassLoader( - new URL[]{ jarFile.toUri().toURL() }, - BlueMap.class.getClassLoader() - ); - entrypointClass = addonClassLoader.loadClass(addonInfo.getEntrypoint()); - } - - // create addon instance - Object instance = entrypointClass.getConstructor().newInstance(); - LoadedAddon addon = new LoadedAddon( - addonInfo, - addonClassLoader, - instance - ); - LOADED_ADDONS.put(addonInfo.getId(), addon); - - // run addon - if (instance instanceof Runnable runnable) - runnable.run(); - - } catch (Exception e) { - throw new ConfigurationException("There was an exception trying to initialize the addon!", e); - } - } - - public static @Nullable AddonInfo loadAddonInfo(Path addonJarFile) throws ConfigurationException { - try (FileSystem fileSystem = FileSystems.newFileSystem(addonJarFile, (ClassLoader) null)) { - for (Path root : fileSystem.getRootDirectories()) { - Path addonInfoFile = root.resolve(ADDON_INFO_FILE); - if (!Files.exists(addonInfoFile)) continue; - - try (Reader reader = Files.newBufferedReader(addonInfoFile, StandardCharsets.UTF_8)) { - AddonInfo addonInfo = GSON.fromJson(reader, AddonInfo.class); - - if (addonInfo.getId() == null) - throw new ConfigurationException("'id' is missing"); - - if (addonInfo.getEntrypoint() == null) - throw new ConfigurationException("'entrypoint' is missing"); - - return addonInfo; - } - } - } catch (IOException e) { - throw new ConfigurationException("There was an exception trying to access the file.", e); - } - - return null; - } - - private static ConfigurationException createRichExceptionForFile(Path jarFile) { - boolean isPlugin = false; - boolean isMod = false; - - try (FileSystem fileSystem = FileSystems.newFileSystem(jarFile, (ClassLoader) null)) { - for (Path root : fileSystem.getRootDirectories()) { - if (Files.exists(root.resolve(PLUGIN_YML))) isPlugin = true; - if (Files.exists(root.resolve(MODS_TOML))) isMod = true; - if (Files.exists(root.resolve(FABRIC_MOD_JSON))) isMod = true; - } - } catch (IOException e) { - Logger.global.logError("Failed to log file-info for '%s'".formatted(jarFile), e); - } - - if (!(isPlugin || isMod)) return new ConfigurationException(""" - File '%s' does not seem to be a valid native bluemap addon. - """.strip().formatted(jarFile)); - - String type = isPlugin ? "plugin" : "mod"; - String targetFolder = isPlugin ? "./plugins" : "./mods"; - - return new ConfigurationException(""" - File '%s' seems to be a %s and not a native bluemap addon. - Try adding it to the '%s' folder of your server instead! - """.strip().formatted(jarFile, type, targetFolder)); - } - -} diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/CombinedClassLoader.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/CombinedClassLoader.java new file mode 100644 index 000000000..c5a15ad80 --- /dev/null +++ b/common/src/main/java/de/bluecolored/bluemap/common/addons/CombinedClassLoader.java @@ -0,0 +1,53 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.common.addons; + +import java.util.Arrays; +import java.util.Objects; + +public class CombinedClassLoader extends ClassLoader { + + private final ClassLoader[] delegates; + + public CombinedClassLoader(ClassLoader parent, ClassLoader... delegates) { + super(parent); + if (delegates.length == 0) throw new IllegalArgumentException("No parent classloaders provided"); + if (Arrays.stream(delegates).anyMatch(Objects::isNull)) throw new IllegalArgumentException("Parent classloaders can not be null"); + this.delegates = delegates; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + for (ClassLoader parent : delegates) { + try { + return parent.loadClass(name); + } catch (ClassNotFoundException ignore) { + // ignore ClassNotFoundException thrown if class not found in parent + } + } + + throw new ClassNotFoundException(name); + } +} diff --git a/common/src/main/java/de/bluecolored/bluemap/common/addons/LoadedAddon.java b/common/src/main/java/de/bluecolored/bluemap/common/addons/LoadedAddon.java index 0d5db41b5..ec8a59717 100644 --- a/common/src/main/java/de/bluecolored/bluemap/common/addons/LoadedAddon.java +++ b/common/src/main/java/de/bluecolored/bluemap/common/addons/LoadedAddon.java @@ -24,8 +24,20 @@ */ package de.bluecolored.bluemap.common.addons; -public record LoadedAddon ( - AddonInfo addonInfo, - ClassLoader classLoader, - Object instance -) {} +import lombok.Getter; + +import java.nio.file.Path; + +@Getter +public class LoadedAddon extends Addon { + + private final ClassLoader classLoader; + private final Object instance; + + public LoadedAddon(AddonInfo addonInfo, Path jarFile, ClassLoader classLoader, Object instance) { + super(addonInfo, jarFile); + this.classLoader = classLoader; + this.instance = instance; + } + +} diff --git a/common/src/main/java/de/bluecolored/bluemap/common/config/ConfigurationException.java b/common/src/main/java/de/bluecolored/bluemap/common/config/ConfigurationException.java index 5848446b0..bed164535 100644 --- a/common/src/main/java/de/bluecolored/bluemap/common/config/ConfigurationException.java +++ b/common/src/main/java/de/bluecolored/bluemap/common/config/ConfigurationException.java @@ -24,6 +24,8 @@ */ package de.bluecolored.bluemap.common.config; +import de.bluecolored.bluemap.core.logger.Logger; + public class ConfigurationException extends Exception { private static final String FORMATTING_BAR = "################################"; @@ -51,12 +53,10 @@ public ConfigurationException(String message, String explanation, Throwable caus } public Throwable getRootCause() { - Throwable cause = getCause(); - if (cause instanceof ConfigurationException) { - return ((ConfigurationException) cause).getRootCause(); - } else { - return cause; - } + Throwable cause; + do { cause = getCause(); } + while (cause instanceof ConfigurationException); + return cause; } public String getExplanation() { @@ -84,4 +84,11 @@ public String getFormattedExplanation() { "\n" + FORMATTING_BAR; } + public void printLog(Logger logger) { + logger.logWarning(getFormattedExplanation()); + Throwable cause = getRootCause(); + if (cause != null) + logger.logError("Detailed error:", this); + } + } diff --git a/common/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/common/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index 920564d83..10e013501 100644 --- a/common/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/common/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -28,7 +28,7 @@ import de.bluecolored.bluemap.common.BlueMapService; import de.bluecolored.bluemap.common.InterruptableReentrantLock; import de.bluecolored.bluemap.common.MissingResourcesException; -import de.bluecolored.bluemap.common.addons.Addons; +import de.bluecolored.bluemap.common.addons.AddonLoader; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.config.*; import de.bluecolored.bluemap.common.debug.StateDumper; @@ -126,7 +126,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti //load addons Path packsFolder = serverInterface.getConfigFolder().resolve("packs"); Files.createDirectories(packsFolder); - Addons.tryLoadAddons(packsFolder); + AddonLoader.INSTANCE.tryLoadAddons(packsFolder); //load configs BlueMapConfigManager configManager = BlueMapConfigManager.builder() diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 451c63ce1..261c76576 100644 --- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -28,7 +28,7 @@ import de.bluecolored.bluemap.common.BlueMapConfiguration; import de.bluecolored.bluemap.common.BlueMapService; import de.bluecolored.bluemap.common.MissingResourcesException; -import de.bluecolored.bluemap.common.addons.Addons; +import de.bluecolored.bluemap.common.addons.AddonLoader; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.commands.TextFormat; import de.bluecolored.bluemap.common.config.*; @@ -363,7 +363,7 @@ public static void main(String[] args) { // load addons Path packsFolder = cli.configFolder.resolve("packs"); Files.createDirectories(packsFolder); - Addons.tryLoadAddons(packsFolder); + AddonLoader.INSTANCE.tryLoadAddons(packsFolder); // load configs BlueMapConfigManager configs = BlueMapConfigManager.builder() @@ -449,11 +449,7 @@ public static void main(String[] args) { BlueMapCLI.printHelp(); System.exit(1); } catch (ConfigurationException e) { - Logger.global.logWarning(e.getFormattedExplanation()); - Throwable cause = e.getRootCause(); - if (cause != null) { - Logger.global.logError("Detailed error:", e); - } + e.printLog(Logger.global); } catch (IOException e) { Logger.global.logError("An IO-error occurred!", e); System.exit(1);