mirror of
				https://github.com/PlaceholderAPI/PlaceholderAPI
				synced 2025-10-31 06:12:28 +01:00 
			
		
		
		
	rewrote discovery and registration code to be composable and higher level
This commit is contained in:
		| @@ -5,16 +5,16 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; | |||||||
| import me.clip.placeholderapi.commands.PlaceholderCommand; | import me.clip.placeholderapi.commands.PlaceholderCommand; | ||||||
| import me.clip.placeholderapi.expansion.PlaceholderExpansion; | import me.clip.placeholderapi.expansion.PlaceholderExpansion; | ||||||
| import me.clip.placeholderapi.expansion.cloud.CloudExpansion; | import me.clip.placeholderapi.expansion.cloud.CloudExpansion; | ||||||
|  | import me.clip.placeholderapi.util.Futures; | ||||||
| import me.clip.placeholderapi.util.Msg; | import me.clip.placeholderapi.util.Msg; | ||||||
| import org.bukkit.Bukkit; |  | ||||||
| import org.bukkit.command.CommandSender; | import org.bukkit.command.CommandSender; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
|  | import org.jetbrains.annotations.Nullable; | ||||||
| import org.jetbrains.annotations.Unmodifiable; | import org.jetbrains.annotations.Unmodifiable; | ||||||
|  |  | ||||||
| import java.io.File; |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| import java.util.concurrent.CompletableFuture; | import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| @@ -67,9 +67,8 @@ public final class CommandECloudUpdate extends PlaceholderCommand | |||||||
| 		Msg.msg(sender, | 		Msg.msg(sender, | ||||||
| 				"&aUpdating expansions: " + expansions.stream().map(CloudExpansion::getName).collect(Collectors.joining("&7, &6", "&8[&6", "&8]&r"))); | 				"&aUpdating expansions: " + expansions.stream().map(CloudExpansion::getName).collect(Collectors.joining("&7, &6", "&8[&6", "&8]&r"))); | ||||||
|  |  | ||||||
| 		downloadExpansions(plugin, expansions) |  | ||||||
| 				.thenCompose(files -> discoverExpansions(plugin, files)) | 		Futures.onMainThread(plugin, downloadAndDiscover(expansions, plugin), (classes, exception) -> { | ||||||
| 				.whenComplete((classes, exception) -> { |  | ||||||
| 			if (exception != null) | 			if (exception != null) | ||||||
| 			{ | 			{ | ||||||
| 				Msg.msg(sender, | 				Msg.msg(sender, | ||||||
| @@ -80,17 +79,18 @@ public final class CommandECloudUpdate extends PlaceholderCommand | |||||||
| 			Msg.msg(sender, | 			Msg.msg(sender, | ||||||
| 					"&aSuccessfully downloaded updates, registering new versions."); | 					"&aSuccessfully downloaded updates, registering new versions."); | ||||||
|  |  | ||||||
| 					Bukkit.getScheduler().runTask(plugin, () -> { |  | ||||||
| 			final String message = classes.stream() | 			final String message = classes.stream() | ||||||
|  | 										  .filter(Objects::nonNull) | ||||||
| 										  .map(plugin.getLocalExpansionManager()::register) | 										  .map(plugin.getLocalExpansionManager()::register) | ||||||
| 										  .filter(Optional::isPresent) | 										  .filter(Optional::isPresent) | ||||||
| 										  .map(Optional::get) | 										  .map(Optional::get) | ||||||
| 										  .map(expansion -> "  &a" + expansion.getName() + " &f" + expansion.getVersion()) | 										  .map(expansion -> "  &a" + expansion.getName() + " &f" + expansion.getVersion()) | ||||||
| 										  .collect(Collectors.joining("\n")); | 										  .collect(Collectors.joining("\n")); | ||||||
|  |  | ||||||
| 			Msg.msg(sender, | 			Msg.msg(sender, | ||||||
| 								"&7Registered expansions:", | 					"&7Registered expansions:", message); | ||||||
| 								message); |  | ||||||
| 					}); |  | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -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() | 		return expansions.stream() | ||||||
| 						 .map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion())) | 						 .map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion())) | ||||||
| 																.collect(Collectors.toList()); | 						 .map(future -> future.thenCompose(plugin.getLocalExpansionManager()::findExpansionInFile)) | ||||||
|  | 						 .collect(Futures.collector()); | ||||||
| 		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())); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; | |||||||
| import me.clip.placeholderapi.commands.PlaceholderCommand; | import me.clip.placeholderapi.commands.PlaceholderCommand; | ||||||
| import me.clip.placeholderapi.expansion.PlaceholderExpansion; | import me.clip.placeholderapi.expansion.PlaceholderExpansion; | ||||||
| import me.clip.placeholderapi.expansion.manager.LocalExpansionManager; | import me.clip.placeholderapi.expansion.manager.LocalExpansionManager; | ||||||
|  | import me.clip.placeholderapi.util.Futures; | ||||||
| import me.clip.placeholderapi.util.Msg; | import me.clip.placeholderapi.util.Msg; | ||||||
| import org.bukkit.Bukkit; |  | ||||||
| import org.bukkit.command.CommandSender; | import org.bukkit.command.CommandSender; | ||||||
| import org.jetbrains.annotations.NotNull; | import org.jetbrains.annotations.NotNull; | ||||||
| import org.jetbrains.annotations.Unmodifiable; | import org.jetbrains.annotations.Unmodifiable; | ||||||
| @@ -45,7 +45,7 @@ public final class CommandExpansionRegister extends PlaceholderCommand | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		manager.findExpansionsInFile(file).whenComplete((classes, exception) -> { | 		Futures.onMainThread(plugin, manager.findExpansionInFile(file), (clazz, exception) -> { | ||||||
| 			if (exception != null) | 			if (exception != null) | ||||||
| 			{ | 			{ | ||||||
| 				Msg.msg(sender, | 				Msg.msg(sender, | ||||||
| @@ -55,15 +55,15 @@ public final class CommandExpansionRegister extends PlaceholderCommand | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (classes.isEmpty()) | 			if (clazz == null) | ||||||
| 			{ | 			{ | ||||||
| 				Msg.msg(sender, | 				Msg.msg(sender, | ||||||
| 						"&cNo expansion class found in file: &f" + file); | 						"&cNo expansion class found in file: &f" + file); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Bukkit.getScheduler().runTask(plugin, () -> { |  | ||||||
| 				final Optional<PlaceholderExpansion> expansion = manager.register(classes.get(0)); | 			final Optional<PlaceholderExpansion> expansion = manager.register(clazz); | ||||||
| 			if (!expansion.isPresent()) | 			if (!expansion.isPresent()) | ||||||
| 			{ | 			{ | ||||||
| 				Msg.msg(sender, | 				Msg.msg(sender, | ||||||
| @@ -73,7 +73,7 @@ public final class CommandExpansionRegister extends PlaceholderCommand | |||||||
|  |  | ||||||
| 			Msg.msg(sender, | 			Msg.msg(sender, | ||||||
| 					"&aSuccessfully registered expansion: &f" + expansion.get().getName()); | 					"&aSuccessfully registered expansion: &f" + expansion.get().getName()); | ||||||
| 			}); |  | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import me.clip.placeholderapi.expansion.Taskable; | |||||||
| import me.clip.placeholderapi.expansion.VersionSpecific; | import me.clip.placeholderapi.expansion.VersionSpecific; | ||||||
| import me.clip.placeholderapi.expansion.cloud.CloudExpansion; | import me.clip.placeholderapi.expansion.cloud.CloudExpansion; | ||||||
| import me.clip.placeholderapi.util.FileUtil; | import me.clip.placeholderapi.util.FileUtil; | ||||||
|  | import me.clip.placeholderapi.util.Futures; | ||||||
| import me.clip.placeholderapi.util.Msg; | import me.clip.placeholderapi.util.Msg; | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.command.CommandSender; | import org.bukkit.command.CommandSender; | ||||||
| @@ -28,7 +29,7 @@ import org.jetbrains.annotations.Nullable; | |||||||
| import org.jetbrains.annotations.Unmodifiable; | import org.jetbrains.annotations.Unmodifiable; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.IOException; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -290,19 +291,18 @@ public final class LocalExpansionManager implements Listener | |||||||
| 	{ | 	{ | ||||||
| 		plugin.getLogger().info("Placeholder expansion registration initializing..."); | 		plugin.getLogger().info("Placeholder expansion registration initializing..."); | ||||||
|  |  | ||||||
| 		findExpansionsOnDisk().whenCompleteAsync((classes, exception) -> { | 		Futures.onMainThread(plugin, findExpansionsOnDisk(), (classes, exception) -> { | ||||||
| 			if (exception != null) | 			if (exception != null) | ||||||
| 			{ | 			{ | ||||||
| 				plugin.getLogger().log(Level.SEVERE, "failed to load class files of expansions", exception); | 				plugin.getLogger().log(Level.SEVERE, "failed to load class files of expansions", exception); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			Bukkit.getScheduler().runTask(plugin, () -> { |  | ||||||
| 			final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count(); | 			final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count(); | ||||||
|  |  | ||||||
| 			Msg.msg(sender, | 			Msg.msg(sender, | ||||||
| 					registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!"); | 					registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!"); | ||||||
| 		}); | 		}); | ||||||
| 		}); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private void unregisterAll() | 	private void unregisterAll() | ||||||
| @@ -320,33 +320,26 @@ public final class LocalExpansionManager implements Listener | |||||||
|  |  | ||||||
|  |  | ||||||
| 	@NotNull | 	@NotNull | ||||||
| 	public CompletableFuture<List<Class<? extends PlaceholderExpansion>>> findExpansionsOnDisk() | 	public CompletableFuture<@NotNull List<@NotNull Class<? extends PlaceholderExpansion>>> findExpansionsOnDisk() | ||||||
| 	{ | 	{ | ||||||
| 		return CompletableFuture.supplyAsync(() -> { | 		return Arrays.stream(folder.listFiles((dir, name) -> name.endsWith(".jar"))) | ||||||
| 			try | 					 .map(this::findExpansionInFile) | ||||||
| 			{ | 					 .collect(Futures.collector()); | ||||||
| 				return FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class); |  | ||||||
| 			} |  | ||||||
| 			catch (final IOException | ClassNotFoundException ex) |  | ||||||
| 			{ |  | ||||||
| 				throw new CompletionException(ex); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@NotNull | 	@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(() -> { | 		return CompletableFuture.supplyAsync(() -> { | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
| 				final List<@NotNull Class<? extends PlaceholderExpansion>> classes = FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class, file.getName()); | 				return FileUtil.findClass(file, PlaceholderExpansion.class); | ||||||
| 				if (classes.size() > 1) |  | ||||||
| 				{ |  | ||||||
| 					throw new IllegalStateException("multiple expansion classes in one file!"); |  | ||||||
| 			} | 			} | ||||||
|  | 			catch (final VerifyError ex) | ||||||
| 				return classes; | 			{ | ||||||
|  | 				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) | 			catch (final Exception ex) | ||||||
| 			{ | 			{ | ||||||
| @@ -365,6 +358,11 @@ public final class LocalExpansionManager implements Listener | |||||||
| 		} | 		} | ||||||
| 		catch (final Exception ex) | 		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); | 			plugin.getLogger().log(Level.SEVERE, "Failed to load placeholder expansion from class: " + clazz.getName(), ex); | ||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -24,51 +24,25 @@ import org.jetbrains.annotations.NotNull; | |||||||
| import org.jetbrains.annotations.Nullable; | import org.jetbrains.annotations.Nullable; | ||||||
|  |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.FilenameFilter; |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.net.URL; | import java.net.URL; | ||||||
| import java.net.URLClassLoader; | 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.JarEntry; | ||||||
| import java.util.jar.JarInputStream; | import java.util.jar.JarInputStream; | ||||||
|  |  | ||||||
| public class FileUtil | public class FileUtil | ||||||
| { | { | ||||||
|  |  | ||||||
| 	@NotNull | 	@Nullable | ||||||
| 	public static <T> List<@NotNull Class<? extends T>> getClasses(@NotNull final File folder, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException | 	public static <T> Class<? extends T> findClass(@NotNull final File file, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException | ||||||
| 	{ | 	{ | ||||||
| 		return getClasses(folder, clazz, null); | 		if (!file.exists()) | ||||||
|  | 		{ | ||||||
|  | 			return null; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	@NotNull | 		final URL jar = file.toURI().toURL(); | ||||||
| 	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()) |  | ||||||
| 		{ |  | ||||||
| 			return Collections.emptyList(); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		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 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())) | 		try (final URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); final JarInputStream stream = new JarInputStream(jar.openStream())) | ||||||
| 		{ | 		{ | ||||||
| 			JarEntry entry; | 			JarEntry entry; | ||||||
| @@ -85,7 +59,7 @@ public class FileUtil | |||||||
| 					final Class<?> loaded = loader.loadClass(name.substring(0, name.lastIndexOf('.')).replace('/', '.')); | 					final Class<?> loaded = loader.loadClass(name.substring(0, name.lastIndexOf('.')).replace('/', '.')); | ||||||
| 					if (clazz.isAssignableFrom(loaded)) | 					if (clazz.isAssignableFrom(loaded)) | ||||||
| 					{ | 					{ | ||||||
| 						list.add(loaded.asSubclass(clazz)); | 						return loaded.asSubclass(clazz); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				catch (final NoClassDefFoundError ignored) | 				catch (final NoClassDefFoundError ignored) | ||||||
| @@ -93,6 +67,8 @@ public class FileUtil | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		return null; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								src/main/java/me/clip/placeholderapi/util/Futures.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/main/java/me/clip/placeholderapi/util/Futures.java
									
									
									
									
									
										Normal 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()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user