diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java index 9d9399c..91498e5 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java @@ -5,16 +5,16 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.cloud.CloudExpansion; +import me.clip.placeholderapi.util.Futures; import me.clip.placeholderapi.util.Msg; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; -import java.io.File; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -67,31 +67,31 @@ public final class CommandECloudUpdate extends PlaceholderCommand Msg.msg(sender, "&aUpdating expansions: " + expansions.stream().map(CloudExpansion::getName).collect(Collectors.joining("&7, &6", "&8[&6", "&8]&r"))); - downloadExpansions(plugin, expansions) - .thenCompose(files -> discoverExpansions(plugin, files)) - .whenComplete((classes, exception) -> { - if (exception != null) - { - Msg.msg(sender, - "&cFailed to update expansions: &e" + exception.getMessage()); - return; - } - Msg.msg(sender, - "&aSuccessfully downloaded updates, registering new versions."); + Futures.onMainThread(plugin, downloadAndDiscover(expansions, plugin), (classes, exception) -> { + if (exception != null) + { + Msg.msg(sender, + "&cFailed to update expansions: &e" + exception.getMessage()); + return; + } - Bukkit.getScheduler().runTask(plugin, () -> { - final String message = classes.stream() - .map(plugin.getLocalExpansionManager()::register) - .filter(Optional::isPresent) - .map(Optional::get) - .map(expansion -> " &a" + expansion.getName() + " &f" + expansion.getVersion()) - .collect(Collectors.joining("\n")); - Msg.msg(sender, - "&7Registered expansions:", - message); - }); - }); + Msg.msg(sender, + "&aSuccessfully downloaded updates, registering new versions."); + + + final String message = classes.stream() + .filter(Objects::nonNull) + .map(plugin.getLocalExpansionManager()::register) + .filter(Optional::isPresent) + .map(Optional::get) + .map(expansion -> " &a" + expansion.getName() + " &f" + expansion.getVersion()) + .collect(Collectors.joining("\n")); + + Msg.msg(sender, + "&7Registered expansions:", message); + + }); } @Override @@ -114,22 +114,12 @@ public final class CommandECloudUpdate extends PlaceholderCommand } - public static CompletableFuture> downloadExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List expansions) + private static CompletableFuture>> downloadAndDiscover(@NotNull final List expansions, @NotNull final PlaceholderAPIPlugin plugin) { - final List> futures = expansions.stream() - .map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion())) - .collect(Collectors.toList()); - - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApplyAsync(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList())); - } - - public static CompletableFuture>> discoverExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List files) - { - final List>>> futures = files.stream() - .map(file -> plugin.getLocalExpansionManager().findExpansionsInFile(file)) - .collect(Collectors.toList()); - - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApplyAsync(v -> futures.stream().map(CompletableFuture::join).flatMap(Collection::stream).collect(Collectors.toList())); + return expansions.stream() + .map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion())) + .map(future -> future.thenCompose(plugin.getLocalExpansionManager()::findExpansionInFile)) + .collect(Futures.collector()); } } diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java index d1c504b..348b76b 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java @@ -4,8 +4,8 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.commands.PlaceholderCommand; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.manager.LocalExpansionManager; +import me.clip.placeholderapi.util.Futures; import me.clip.placeholderapi.util.Msg; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; @@ -45,7 +45,7 @@ public final class CommandExpansionRegister extends PlaceholderCommand return; } - manager.findExpansionsInFile(file).whenComplete((classes, exception) -> { + Futures.onMainThread(plugin, manager.findExpansionInFile(file), (clazz, exception) -> { if (exception != null) { Msg.msg(sender, @@ -55,25 +55,25 @@ public final class CommandExpansionRegister extends PlaceholderCommand return; } - if (classes.isEmpty()) + if (clazz == null) { Msg.msg(sender, "&cNo expansion class found in file: &f" + file); return; } - Bukkit.getScheduler().runTask(plugin, () -> { - final Optional expansion = manager.register(classes.get(0)); - if (!expansion.isPresent()) - { - Msg.msg(sender, - "&cFailed to register expansion from &f" + params.get(0)); - return; - } + final Optional expansion = manager.register(clazz); + if (!expansion.isPresent()) + { Msg.msg(sender, - "&aSuccessfully registered expansion: &f" + expansion.get().getName()); - }); + "&cFailed to register expansion from &f" + params.get(0)); + return; + } + + Msg.msg(sender, + "&aSuccessfully registered expansion: &f" + expansion.get().getName()); + }); } diff --git a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java index a9088b9..f5689cc 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java @@ -13,6 +13,7 @@ import me.clip.placeholderapi.expansion.Taskable; import me.clip.placeholderapi.expansion.VersionSpecific; import me.clip.placeholderapi.expansion.cloud.CloudExpansion; import me.clip.placeholderapi.util.FileUtil; +import me.clip.placeholderapi.util.Futures; import me.clip.placeholderapi.util.Msg; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -28,7 +29,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; import java.io.File; -import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -290,18 +291,17 @@ public final class LocalExpansionManager implements Listener { plugin.getLogger().info("Placeholder expansion registration initializing..."); - findExpansionsOnDisk().whenCompleteAsync((classes, exception) -> { + Futures.onMainThread(plugin, findExpansionsOnDisk(), (classes, exception) -> { if (exception != null) { plugin.getLogger().log(Level.SEVERE, "failed to load class files of expansions", exception); return; } - Bukkit.getScheduler().runTask(plugin, () -> { - final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count(); - Msg.msg(sender, - registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!"); - }); + final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count(); + + Msg.msg(sender, + registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!"); }); } @@ -320,33 +320,26 @@ public final class LocalExpansionManager implements Listener @NotNull - public CompletableFuture>> findExpansionsOnDisk() + public CompletableFuture<@NotNull List<@NotNull Class>> findExpansionsOnDisk() { - return CompletableFuture.supplyAsync(() -> { - try - { - return FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class); - } - catch (final IOException | ClassNotFoundException ex) - { - throw new CompletionException(ex); - } - }); + return Arrays.stream(folder.listFiles((dir, name) -> name.endsWith(".jar"))) + .map(this::findExpansionInFile) + .collect(Futures.collector()); } @NotNull - public CompletableFuture>> findExpansionsInFile(@NotNull final File file) + public CompletableFuture<@Nullable Class> findExpansionInFile(@NotNull final File file) { return CompletableFuture.supplyAsync(() -> { try { - final List<@NotNull Class> classes = FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class, file.getName()); - if (classes.size() > 1) - { - throw new IllegalStateException("multiple expansion classes in one file!"); - } - - return classes; + return FileUtil.findClass(file, PlaceholderExpansion.class); + } + catch (final VerifyError ex) + { + plugin.getLogger().severe("expansion file " + file.getName() + " is outdated: \n" + + "Failed to load due to a [" + ex.getClass().getSimpleName() + "], attempted to use" + ex.getMessage().substring(ex.getMessage().lastIndexOf(' '))); + return null; } catch (final Exception ex) { @@ -365,6 +358,11 @@ public final class LocalExpansionManager implements Listener } catch (final Exception ex) { + if (ex.getCause() instanceof LinkageError) + { + throw ((LinkageError) ex.getCause()); + } + plugin.getLogger().log(Level.SEVERE, "Failed to load placeholder expansion from class: " + clazz.getName(), ex); return null; } diff --git a/src/main/java/me/clip/placeholderapi/util/FileUtil.java b/src/main/java/me/clip/placeholderapi/util/FileUtil.java index 3510715..0ed981e 100644 --- a/src/main/java/me/clip/placeholderapi/util/FileUtil.java +++ b/src/main/java/me/clip/placeholderapi/util/FileUtil.java @@ -24,51 +24,25 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class FileUtil { - @NotNull - public static List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class clazz) throws IOException, ClassNotFoundException + @Nullable + public static Class findClass(@NotNull final File file, @NotNull final Class clazz) throws IOException, ClassNotFoundException { - return getClasses(folder, clazz, null); - } - - @NotNull - public static List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class clazz, @Nullable final String target) throws IOException, ClassNotFoundException - { - if (!folder.exists()) + if (!file.exists()) { - return Collections.emptyList(); + return null; } - final File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar") && (target == null || name.replace(".jar", "").equalsIgnoreCase(target.replace(".jar", "")))); - if (jars == null) - { - return Collections.emptyList(); - } + final URL jar = file.toURI().toURL(); - final List<@NotNull Class> list = new ArrayList<>(); - - for (final File file : jars) - { - gather(file.toURI().toURL(), clazz, list); - } - - return list; - } - - private static void gather(@NotNull final URL jar, @NotNull final Class clazz, @NotNull final List<@NotNull Class> list) throws IOException, ClassNotFoundException - { try (final URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); final JarInputStream stream = new JarInputStream(jar.openStream())) { JarEntry entry; @@ -85,7 +59,7 @@ public class FileUtil final Class loaded = loader.loadClass(name.substring(0, name.lastIndexOf('.')).replace('/', '.')); if (clazz.isAssignableFrom(loaded)) { - list.add(loaded.asSubclass(clazz)); + return loaded.asSubclass(clazz); } } catch (final NoClassDefFoundError ignored) @@ -93,6 +67,8 @@ public class FileUtil } } } + + return null; } } diff --git a/src/main/java/me/clip/placeholderapi/util/Futures.java b/src/main/java/me/clip/placeholderapi/util/Futures.java new file mode 100644 index 0000000..d689b56 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/Futures.java @@ -0,0 +1,63 @@ +package me.clip.placeholderapi.util; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class Futures +{ + + private Futures() + {} + + + public static void onMainThread(@NotNull final Plugin plugin, @NotNull final CompletableFuture future, @NotNull final BiConsumer consumer) + { + future.whenComplete((value, exception) -> { + if (Bukkit.isPrimaryThread()) + { + consumer.accept(value, exception); + } + else + { + Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception)); + } + }); + } + + + @NotNull + public static Collector, ?, CompletableFuture>> collector() + { + return Collectors.collectingAndThen(Collectors.toList(), Futures::of); + } + + + @NotNull + public static CompletableFuture> of(@NotNull final Stream> futures) + { + return of(futures.collect(Collectors.toList())); + } + + @NotNull + public static CompletableFuture> of(@NotNull final Collection> futures) + { + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApplyAsync($ -> awaitCompletion(futures)); + } + + @NotNull + private static List awaitCompletion(@NotNull final Collection> futures) + { + return futures.stream().map(CompletableFuture::join).collect(Collectors.toList()); + } + +}