rewrote discovery and registration code to be composable and higher level

This commit is contained in:
Sxtanna 2020-07-26 21:02:55 -04:00
parent ee33de5ec8
commit 8360511c50
5 changed files with 139 additions and 112 deletions

View File

@ -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<List<File>> downloadExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List<CloudExpansion> expansions)
private static CompletableFuture<List<@Nullable Class<? extends PlaceholderExpansion>>> downloadAndDiscover(@NotNull final List<CloudExpansion> expansions, @NotNull final PlaceholderAPIPlugin plugin)
{
final List<CompletableFuture<File>> 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<List<Class<? extends PlaceholderExpansion>>> discoverExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List<File> files)
{
final List<CompletableFuture<List<Class<? extends PlaceholderExpansion>>>> 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());
}
}

View File

@ -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<PlaceholderExpansion> expansion = manager.register(classes.get(0));
if (!expansion.isPresent())
{
Msg.msg(sender,
"&cFailed to register expansion from &f" + params.get(0));
return;
}
final Optional<PlaceholderExpansion> 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());
});
}

View File

@ -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<List<Class<? extends PlaceholderExpansion>>> findExpansionsOnDisk()
public CompletableFuture<@NotNull List<@NotNull Class<? extends PlaceholderExpansion>>> 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<List<Class<? extends PlaceholderExpansion>>> findExpansionsInFile(@NotNull final File file)
public CompletableFuture<@Nullable Class<? extends PlaceholderExpansion>> findExpansionInFile(@NotNull final File file)
{
return CompletableFuture.supplyAsync(() -> {
try
{
final List<@NotNull Class<? extends PlaceholderExpansion>> 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;
}

View File

@ -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 <T> List<@NotNull Class<? extends T>> getClasses(@NotNull final File folder, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException
@Nullable
public static <T> Class<? extends T> findClass(@NotNull final File file, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException
{
return getClasses(folder, clazz, null);
}
@NotNull
public static <T> List<@NotNull Class<? extends T>> getClasses(@NotNull final File folder, @NotNull final Class<T> 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<? extends T>> list = new ArrayList<>();
for (final File file : jars)
{
gather(file.toURI().toURL(), clazz, list);
}
return list;
}
private static <T> void gather(@NotNull final URL jar, @NotNull final Class<T> clazz, @NotNull final List<@NotNull Class<? extends T>> 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;
}
}

View File

@ -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 <T> void onMainThread(@NotNull final Plugin plugin, @NotNull final CompletableFuture<T> future, @NotNull final BiConsumer<T, Throwable> consumer)
{
future.whenComplete((value, exception) -> {
if (Bukkit.isPrimaryThread())
{
consumer.accept(value, exception);
}
else
{
Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception));
}
});
}
@NotNull
public static <T> Collector<CompletableFuture<T>, ?, CompletableFuture<List<T>>> collector()
{
return Collectors.collectingAndThen(Collectors.toList(), Futures::of);
}
@NotNull
public static <T> CompletableFuture<List<T>> of(@NotNull final Stream<CompletableFuture<T>> futures)
{
return of(futures.collect(Collectors.toList()));
}
@NotNull
public static <T> CompletableFuture<List<T>> of(@NotNull final Collection<CompletableFuture<T>> futures)
{
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApplyAsync($ -> awaitCompletion(futures));
}
@NotNull
private static <T> List<T> awaitCompletion(@NotNull final Collection<CompletableFuture<T>> futures)
{
return futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
}