reupload files
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
/**
|
||||
* A thread-safe interval counter, allows to detect if an event happens more than 'threshold' times
|
||||
* in the given 'interval'.
|
||||
*/
|
||||
public class AtomicIntervalCounter {
|
||||
private final int threshold;
|
||||
private final int interval;
|
||||
private int count;
|
||||
private long lastInsert;
|
||||
|
||||
/**
|
||||
* Constructs a new counter.
|
||||
*
|
||||
* @param threshold the threshold value of the counter.
|
||||
* @param interval the counter interval in milliseconds.
|
||||
*/
|
||||
public AtomicIntervalCounter(int threshold, int interval) {
|
||||
this.threshold = threshold;
|
||||
this.interval = interval;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the counter count.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
count = 0;
|
||||
lastInsert = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the counter and returns true if the current count has reached the threshold value
|
||||
* in the given interval, this will also reset the count value.
|
||||
*
|
||||
* @return true if the count has reached the threshold value.
|
||||
*/
|
||||
public synchronized boolean handle() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastInsert > interval) {
|
||||
count = 1;
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
if (count > threshold) {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
lastInsert = now;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utilities for exceptions.
|
||||
*/
|
||||
public final class ExceptionUtils {
|
||||
|
||||
private ExceptionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first throwable of the given {@code wantedThrowableType} by visiting the provided
|
||||
* throwable and its causes recursively.
|
||||
*
|
||||
* @param wantedThrowableType the throwable type to find
|
||||
* @param throwable the throwable to start with
|
||||
* @param <T> the desired throwable subtype
|
||||
* @return the first throwable found of the given type, or null if none found
|
||||
*/
|
||||
public static <T extends Throwable> T findThrowableInCause(Class<T> wantedThrowableType, Throwable throwable) {
|
||||
Set<Throwable> visitedObjects = Sets.newIdentityHashSet();
|
||||
Throwable currentThrowable = throwable;
|
||||
while (currentThrowable != null && !visitedObjects.contains(currentThrowable)) {
|
||||
if (wantedThrowableType.isInstance(currentThrowable)) {
|
||||
return wantedThrowableType.cast(currentThrowable);
|
||||
}
|
||||
visitedObjects.add(currentThrowable);
|
||||
currentThrowable = currentThrowable.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the information from a Throwable as string, retaining the type and its message.
|
||||
*
|
||||
* @param th the throwable to process
|
||||
* @return string with the type of the Throwable and its message, e.g. "[IOException]: Could not open stream"
|
||||
*/
|
||||
public static String formatException(Throwable th) {
|
||||
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import fr.xephi.authme.AuthMe;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* File utilities.
|
||||
*/
|
||||
public final class FileUtils {
|
||||
|
||||
private static final DateTimeFormatter CURRENT_DATE_STRING_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyyMMdd_HHmm");
|
||||
|
||||
private static ConsoleLogger logger = ConsoleLoggerFactory.get(FileUtils.class);
|
||||
|
||||
// Utility class
|
||||
private FileUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a resource file (from the JAR) to the given file if it doesn't exist.
|
||||
*
|
||||
* @param destinationFile The file to check and copy to (outside of JAR)
|
||||
* @param resourcePath Local path to the resource file (path to file within JAR)
|
||||
*
|
||||
* @return False if the file does not exist and could not be copied, true otherwise
|
||||
*/
|
||||
public static boolean copyFileFromResource(File destinationFile, String resourcePath) {
|
||||
if (destinationFile.exists()) {
|
||||
return true;
|
||||
} else if (!createDirectory(destinationFile.getParentFile())) {
|
||||
logger.warning("Cannot create parent directories for '" + destinationFile + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream is = getResourceFromJar(resourcePath)) {
|
||||
if (is == null) {
|
||||
logger.warning(format("Cannot copy resource '%s' to file '%s': cannot load resource",
|
||||
resourcePath, destinationFile.getPath()));
|
||||
} else {
|
||||
java.nio.file.Files.copy(is, destinationFile.toPath());
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.logException(format("Cannot copy resource '%s' to file '%s':",
|
||||
resourcePath, destinationFile.getPath()), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given directory.
|
||||
*
|
||||
* @param dir the directory to create
|
||||
* @return true upon success, false otherwise
|
||||
*/
|
||||
public static boolean createDirectory(File dir) {
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
logger.warning("Could not create directory '" + dir + "'");
|
||||
return false;
|
||||
}
|
||||
return dir.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JAR file as stream. Returns null if it doesn't exist.
|
||||
*
|
||||
* @param path the local path (starting from resources project, e.g. "config.yml" for 'resources/config.yml')
|
||||
* @return the stream if the file exists, or false otherwise
|
||||
*/
|
||||
public static InputStream getResourceFromJar(String path) {
|
||||
// ClassLoader#getResourceAsStream does not deal with the '\' path separator: replace to '/'
|
||||
final String normalizedPath = path.replace("\\", "/");
|
||||
return AuthMe.class.getClassLoader().getResourceAsStream(normalizedPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given directory and all its content.
|
||||
*
|
||||
* @param directory The directory to remove
|
||||
*/
|
||||
public static void purgeDirectory(File directory) {
|
||||
if (!directory.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File target : files) {
|
||||
if (target.isDirectory()) {
|
||||
purgeDirectory(target);
|
||||
}
|
||||
delete(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given file or directory and log a message if it was unsuccessful.
|
||||
* Method is null safe and does nothing when null is passed.
|
||||
*
|
||||
* @param file the file to delete
|
||||
*/
|
||||
public static void delete(File file) {
|
||||
if (file != null) {
|
||||
boolean result = file.delete();
|
||||
if (!result) {
|
||||
logger.warning("Could not delete file '" + file + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given file or throws an exception.
|
||||
*
|
||||
* @param file the file to create
|
||||
*/
|
||||
public static void create(File file) {
|
||||
try {
|
||||
boolean result = file.createNewFile();
|
||||
if (!result) {
|
||||
throw new IllegalStateException("Could not create file '" + file + "'");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error while creating file '" + file + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a file path from the given elements, i.e. separate the given elements by the file separator.
|
||||
*
|
||||
* @param elements The elements to create a path with
|
||||
*
|
||||
* @return The created path
|
||||
*/
|
||||
public static String makePath(String... elements) {
|
||||
return String.join(File.separator, elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a textual representation of the current time (including minutes), e.g. useful for
|
||||
* automatically generated backup files.
|
||||
*
|
||||
* @return string of the current time for use in file names
|
||||
*/
|
||||
public static String createCurrentTimeString() {
|
||||
return LocalDateTime.now().format(CURRENT_DATE_STRING_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to a new file (which doesn't exist yet) with a timestamp in the name in the same
|
||||
* folder as the given file and containing the given file's filename.
|
||||
*
|
||||
* @param file the file based on which a new file path should be created
|
||||
* @return path to a file suitably named for storing a backup
|
||||
*/
|
||||
public static String createBackupFilePath(File file) {
|
||||
String filename = "backup_" + Files.getNameWithoutExtension(file.getName())
|
||||
+ "_" + createCurrentTimeString()
|
||||
+ "." + Files.getFileExtension(file.getName());
|
||||
return makePath(file.getParent(), filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Utility class about the InternetProtocol
|
||||
*/
|
||||
public final class InternetProtocolUtils {
|
||||
|
||||
// Utility class
|
||||
private InternetProtocolUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified address is a private or loopback address
|
||||
*
|
||||
* @param address address to check
|
||||
* @return true if the address is a local (site and link) or loopback address, false otherwise
|
||||
*/
|
||||
public static boolean isLocalAddress(String address) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(address);
|
||||
|
||||
// Examples: 127.0.0.1, localhost or [::1]
|
||||
return isLoopbackAddress(address)
|
||||
// Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated)
|
||||
// Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses
|
||||
|| inetAddress.isSiteLocalAddress()
|
||||
// Example: 169.254.0.0/16, fe80::/10
|
||||
// Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration
|
||||
|| inetAddress.isLinkLocalAddress()
|
||||
// non deprecated unique site-local that java doesn't check yet -> fc00::/7
|
||||
|| isIPv6UniqueSiteLocal(inetAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified address is a loopback address. This can be one of the following:
|
||||
* <ul>
|
||||
* <li>127.0.0.1</li>
|
||||
* <li>localhost</li>
|
||||
* <li>[::1]</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param address address to check
|
||||
* @return true if the address is a loopback one
|
||||
*/
|
||||
public static boolean isLoopbackAddress(String address) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(address);
|
||||
return inetAddress.isLoopbackAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLoopbackAddress(InetAddress address) {
|
||||
return address.isLoopbackAddress();
|
||||
}
|
||||
|
||||
private static boolean isIPv6UniqueSiteLocal(InetAddress address) {
|
||||
// ref: https://en.wikipedia.org/wiki/Unique_local_address
|
||||
|
||||
// currently undefined but could be used in the near future fc00::/8
|
||||
return (address.getAddress()[0] & 0xFF) == 0xFC
|
||||
// in use for unique site-local fd00::/8
|
||||
|| (address.getAddress()[0] & 0xFF) == 0xFD;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Player utilities.
|
||||
*/
|
||||
public final class PlayerUtils {
|
||||
|
||||
// Utility class
|
||||
private PlayerUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP of the given player.
|
||||
*
|
||||
* @param player The player to return the IP address for
|
||||
* @return The player's IP address
|
||||
*/
|
||||
public static String getPlayerIp(Player player) {
|
||||
return player.getAddress().getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the player is an NPC or not.
|
||||
*
|
||||
* @param player The player to check
|
||||
* @return True if the player is an NPC, false otherwise
|
||||
*/
|
||||
public static boolean isNpc(Player player) {
|
||||
return player.hasMetadata("NPC");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Utility for generating random strings.
|
||||
*/
|
||||
public final class RandomStringUtils {
|
||||
|
||||
private static final char[] CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
|
||||
private static final Random RANDOM = new SecureRandom();
|
||||
private static final int NUM_INDEX = 10;
|
||||
private static final int LOWER_ALPHANUMERIC_INDEX = 36;
|
||||
private static final int HEX_MAX_INDEX = 16;
|
||||
|
||||
// Utility class
|
||||
private RandomStringUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a string of the given length consisting of random characters within the range [0-9a-z].
|
||||
*
|
||||
* @param length The length of the random string to generate
|
||||
* @return The random string
|
||||
*/
|
||||
public static String generate(int length) {
|
||||
return generateString(length, LOWER_ALPHANUMERIC_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random hexadecimal string of the given length. In other words, the generated string
|
||||
* contains characters only within the range [0-9a-f].
|
||||
*
|
||||
* @param length The length of the random string to generate
|
||||
* @return The random hexadecimal string
|
||||
*/
|
||||
public static String generateHex(int length) {
|
||||
return generateString(length, HEX_MAX_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random numbers string of the given length. In other words, the generated string
|
||||
* contains characters only within the range [0-9].
|
||||
*
|
||||
* @param length The length of the random string to generate
|
||||
* @return The random numbers string
|
||||
*/
|
||||
public static String generateNum(int length) {
|
||||
return generateString(length, NUM_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random string with digits and lowercase and uppercase letters. The result of this
|
||||
* method matches the pattern [0-9a-zA-Z].
|
||||
*
|
||||
* @param length The length of the random string to generate
|
||||
* @return The random string
|
||||
*/
|
||||
public static String generateLowerUpper(int length) {
|
||||
return generateString(length, CHARS.length);
|
||||
}
|
||||
|
||||
private static String generateString(int length, int maxIndex) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("Length must be positive but was " + length);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
sb.append(CHARS[RANDOM.nextInt(maxIndex)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import net.ricecode.similarity.LevenshteinDistanceStrategy;
|
||||
import net.ricecode.similarity.StringSimilarityService;
|
||||
import net.ricecode.similarity.StringSimilarityServiceImpl;
|
||||
|
||||
/**
|
||||
* Utility class for String operations.
|
||||
*/
|
||||
public final class StringUtils {
|
||||
|
||||
// Utility class
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the difference of two strings.
|
||||
*
|
||||
* @param first first string
|
||||
* @param second second string
|
||||
* @return the difference value
|
||||
*/
|
||||
public static double getDifference(String first, String second) {
|
||||
// Make sure the strings are valid.
|
||||
if (first == null || second == null) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Create a string similarity service instance, to allow comparison
|
||||
StringSimilarityService service = new StringSimilarityServiceImpl(new LevenshteinDistanceStrategy());
|
||||
|
||||
// Determine the difference value, return the result
|
||||
return Math.abs(service.score(first, second) - 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given string contains any of the provided elements.
|
||||
*
|
||||
* @param str the string to analyze
|
||||
* @param pieces the items to check the string for
|
||||
* @return true if the string contains at least one of the items
|
||||
*/
|
||||
public static boolean containsAny(String str, Iterable<String> pieces) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
for (String piece : pieces) {
|
||||
if (piece != null && str.contains(piece)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method for checking whether a string is empty. Note that the string
|
||||
* is trimmed, so this method also considers a string with whitespace as empty.
|
||||
*
|
||||
* @param str the string to verify
|
||||
* @return true if the string is empty, false otherwise
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given needle is in the middle of the haystack, i.e. that the haystack
|
||||
* contains the needle and that it is not at the very start or end.
|
||||
*
|
||||
* @param needle the needle to search for
|
||||
* @param haystack the haystack to search in
|
||||
* @return true if the needle is in the middle of the word, false otherwise
|
||||
*/
|
||||
// Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like
|
||||
// isInsideString("11", "2211") would unexpectedly return true...
|
||||
public static boolean isInsideString(char needle, String haystack) {
|
||||
int index = haystack.indexOf(needle);
|
||||
return index > 0 && index < haystack.length() - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
//
|
||||
//public class TeleportUtils {
|
||||
// public static void teleport(Player player, Location location) {
|
||||
// try {
|
||||
// Class<?> paperClass = Class.forName("com.destroystokyo.paper.PaperConfig");
|
||||
// // Paper API is loaded, use teleportAsync
|
||||
// Method teleportAsyncMethod = player.getClass().getMethod("teleportAsync", Location.class);
|
||||
// teleportAsyncMethod.setAccessible(true);
|
||||
// teleportAsyncMethod.invoke(player, location);
|
||||
// } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
|
||||
// // Paper API is not loaded, use normal teleport
|
||||
// player.teleport(location);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
public class TeleportUtils {
|
||||
private static Method teleportAsyncMethod;
|
||||
|
||||
static {
|
||||
try {//Detect Paper class
|
||||
Class<?> paperClass = Class.forName("com.destroystokyo.paper.PaperConfig");
|
||||
teleportAsyncMethod = Player.class.getMethod("teleportAsync", Location.class);
|
||||
// if detected,use teleportAsync()
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
teleportAsyncMethod = null;
|
||||
//if not, set method to null
|
||||
}
|
||||
}
|
||||
|
||||
public static void teleport(Player player, Location location) {
|
||||
if (teleportAsyncMethod != null) {
|
||||
try {
|
||||
teleportAsyncMethod.setAccessible(true);
|
||||
teleportAsyncMethod.invoke(player, location);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
player.teleport(location);
|
||||
}
|
||||
} else {
|
||||
player.teleport(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.output.ConsoleLoggerFactory;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility class for various operations used in the codebase.
|
||||
*/
|
||||
public final class Utils {
|
||||
|
||||
/** Number of milliseconds in a minute. */
|
||||
public static final long MILLIS_PER_MINUTE = 60_000L;
|
||||
|
||||
private static ConsoleLogger logger = ConsoleLoggerFactory.get(Utils.class);
|
||||
|
||||
// Utility class
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Pattern sneaky without throwing Exception.
|
||||
*
|
||||
* @param pattern pattern string to compile
|
||||
*
|
||||
* @return the given regex compiled into Pattern object.
|
||||
*/
|
||||
public static Pattern safePatternCompile(String pattern) {
|
||||
try {
|
||||
return Pattern.compile(pattern);
|
||||
} catch (Exception e) {
|
||||
logger.warning("Failed to compile pattern '" + pattern + "' - defaulting to allowing everything");
|
||||
return Pattern.compile(".*?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class exists in the current class loader.
|
||||
*
|
||||
* @param className the class name to check
|
||||
*
|
||||
* @return true if the class is loaded, false otherwise
|
||||
*/
|
||||
public static boolean isClassLoaded(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the given sender (null safe), and logs the message to the console.
|
||||
* This method is aware that the command sender might be the console sender and avoids
|
||||
* displaying the message twice in this case.
|
||||
*
|
||||
* @param sender the sender to inform
|
||||
* @param message the message to log and send
|
||||
*/
|
||||
public static void logAndSendMessage(CommandSender sender, String message) {
|
||||
logger.info(message);
|
||||
// Make sure sender is not console user, which will see the message from ConsoleLogger already
|
||||
if (sender != null && !(sender instanceof ConsoleCommandSender)) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a warning to the given sender (null safe), and logs the warning to the console.
|
||||
* This method is aware that the command sender might be the console sender and avoids
|
||||
* displaying the message twice in this case.
|
||||
*
|
||||
* @param sender the sender to inform
|
||||
* @param message the warning to log and send
|
||||
*/
|
||||
public static void logAndSendWarning(CommandSender sender, String message) {
|
||||
logger.warning(message);
|
||||
// Make sure sender is not console user, which will see the message from ConsoleLogger already
|
||||
if (sender != null && !(sender instanceof ConsoleCommandSender)) {
|
||||
sender.sendMessage(ChatColor.RED + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe way to check whether a collection is empty or not.
|
||||
*
|
||||
* @param coll The collection to verify
|
||||
* @return True if the collection is null or empty, false otherwise
|
||||
*/
|
||||
public static boolean isCollectionEmpty(Collection<?> coll) {
|
||||
return coll == null || coll.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given email is empty or equal to the standard "undefined" email address.
|
||||
*
|
||||
* @param email the email to check
|
||||
*
|
||||
* @return true if the email is empty
|
||||
*/
|
||||
public static boolean isEmailEmpty(String email) {
|
||||
return StringUtils.isBlank(email) || "your@email.com".equalsIgnoreCase(email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for various operations on UUID.
|
||||
*/
|
||||
public final class UuidUtils {
|
||||
|
||||
// Utility class
|
||||
private UuidUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given string as an UUID or null.
|
||||
*
|
||||
* @param string the uuid to parse
|
||||
* @return parsed UUID if succeeded or null
|
||||
*/
|
||||
public static UUID parseUuidSafely(String string) {
|
||||
try {
|
||||
return string == null ? null : UUID.fromString(string);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package fr.xephi.authme.util.expiring;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Represents a duration in time, defined by a time unit and a duration.
|
||||
*/
|
||||
public class Duration {
|
||||
|
||||
private final long duration;
|
||||
private final TimeUnit unit;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param duration the duration
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public Duration(long duration, TimeUnit unit) {
|
||||
this.duration = duration;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Duration object for the given duration and unit in the most suitable time unit.
|
||||
* For example, {@code createWithSuitableUnit(120, TimeUnit.SECONDS)} will return a Duration
|
||||
* object of 2 minutes.
|
||||
* <p>
|
||||
* This method only considers the time units days, hours, minutes, and seconds for the objects
|
||||
* it creates. Conversion is done with {@link TimeUnit#convert} and so always rounds the
|
||||
* results down.
|
||||
* <p>
|
||||
* Further examples:
|
||||
* <code>createWithSuitableUnit(299, TimeUnit.MINUTES); // 4 hours</code>
|
||||
* <code>createWithSuitableUnit(700, TimeUnit.MILLISECONDS); // 0 seconds</code>
|
||||
*
|
||||
* @param sourceDuration the duration
|
||||
* @param sourceUnit the time unit the duration is expressed in
|
||||
* @return Duration object using the most suitable time unit
|
||||
*/
|
||||
public static Duration createWithSuitableUnit(long sourceDuration, TimeUnit sourceUnit) {
|
||||
long durationMillis = Math.abs(TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit));
|
||||
|
||||
TimeUnit targetUnit;
|
||||
if (durationMillis > 1000L * 60L * 60L * 24L) {
|
||||
targetUnit = TimeUnit.DAYS;
|
||||
} else if (durationMillis > 1000L * 60L * 60L) {
|
||||
targetUnit = TimeUnit.HOURS;
|
||||
} else if (durationMillis > 1000L * 60L) {
|
||||
targetUnit = TimeUnit.MINUTES;
|
||||
} else {
|
||||
targetUnit = TimeUnit.SECONDS;
|
||||
}
|
||||
|
||||
long durationInTargetUnit = targetUnit.convert(sourceDuration, sourceUnit);
|
||||
return new Duration(durationInTargetUnit, targetUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the duration
|
||||
*/
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time unit in which the duration is expressed
|
||||
*/
|
||||
public TimeUnit getTimeUnit() {
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package fr.xephi.authme.util.expiring;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Map with expiring entries. Following a configured amount of time after
|
||||
* an entry has been inserted, the map will act as if the entry does not
|
||||
* exist.
|
||||
* <p>
|
||||
* Time starts counting directly after insertion. Inserting a new entry with
|
||||
* a key that already has a value will "reset" the expiration. Although the
|
||||
* expiration can be redefined later on, only entries which are inserted
|
||||
* afterwards will use the new expiration.
|
||||
* <p>
|
||||
* An expiration of {@code <= 0} will make the map expire all entries
|
||||
* immediately after insertion. Note that the map does not remove expired
|
||||
* entries automatically; this is only done when calling
|
||||
* {@link #removeExpiredEntries()}.
|
||||
*
|
||||
* @param <K> the key type
|
||||
* @param <V> the value type
|
||||
*/
|
||||
public class ExpiringMap<K, V> {
|
||||
|
||||
private final Map<K, ExpiringEntry<V>> entries = new ConcurrentHashMap<>();
|
||||
private long expirationMillis;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public ExpiringMap(long duration, TimeUnit unit) {
|
||||
setExpiration(duration, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with the given key,
|
||||
* if available and not expired.
|
||||
*
|
||||
* @param key the key to look up
|
||||
* @return the associated value, or {@code null} if not available
|
||||
*/
|
||||
public V get(K key) {
|
||||
ExpiringEntry<V> value = entries.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
} else if (System.currentTimeMillis() > value.getExpiration()) {
|
||||
entries.remove(key);
|
||||
return null;
|
||||
}
|
||||
return value.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a value for the given key. Overwrites a previous value
|
||||
* for the key if it exists.
|
||||
*
|
||||
* @param key the key to insert a value for
|
||||
* @param value the value to insert
|
||||
*/
|
||||
public void put(K key, V value) {
|
||||
long expiration = System.currentTimeMillis() + expirationMillis;
|
||||
entries.put(key, new ExpiringEntry<>(value, expiration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value for the given key, if available.
|
||||
*
|
||||
* @param key the key to remove the value for
|
||||
*/
|
||||
public void remove(K key) {
|
||||
entries.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all entries which have expired from the internal structure.
|
||||
*/
|
||||
public void removeExpiredEntries() {
|
||||
entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue().getExpiration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new expiration duration. Note that already present entries
|
||||
* will still make use of the old expiration.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public void setExpiration(long duration, TimeUnit unit) {
|
||||
this.expirationMillis = unit.toMillis(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this map is empty. This reflects the state of the
|
||||
* internal map, which may contain expired entries only. The result
|
||||
* may change after running {@link #removeExpiredEntries()}.
|
||||
*
|
||||
* @return true if map is really empty, false otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal map
|
||||
*/
|
||||
protected Map<K, ExpiringEntry<V>> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class holding a value paired with an expiration timestamp.
|
||||
*
|
||||
* @param <V> the value type
|
||||
*/
|
||||
protected static final class ExpiringEntry<V> {
|
||||
|
||||
private final V value;
|
||||
private final long expiration;
|
||||
|
||||
ExpiringEntry(V value, long expiration) {
|
||||
this.value = value;
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package fr.xephi.authme.util.expiring;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Set whose entries expire after a configurable amount of time. Once an entry
|
||||
* has expired, the set will act as if the entry no longer exists. Time starts
|
||||
* counting after the entry has been inserted.
|
||||
* <p>
|
||||
* Internally, expired entries are not guaranteed to be cleared automatically.
|
||||
* A cleanup of all expired entries may be triggered with
|
||||
* {@link #removeExpiredEntries()}. Adding an entry that is already present
|
||||
* effectively resets its expiration.
|
||||
*
|
||||
* @param <E> the type of the entries
|
||||
*/
|
||||
public class ExpiringSet<E> {
|
||||
|
||||
private Map<E, Long> entries = new ConcurrentHashMap<>();
|
||||
private long expirationMillis;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public ExpiringSet(long duration, TimeUnit unit) {
|
||||
setExpiration(duration, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to the set.
|
||||
*
|
||||
* @param entry the entry to add
|
||||
*/
|
||||
public void add(E entry) {
|
||||
entries.put(entry, System.currentTimeMillis() + expirationMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this set contains the given entry, if it hasn't expired.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry is present and not expired, false otherwise
|
||||
*/
|
||||
public boolean contains(E entry) {
|
||||
Long expiration = entries.get(entry);
|
||||
if (expiration == null) {
|
||||
return false;
|
||||
} else if (expiration > System.currentTimeMillis()) {
|
||||
return true;
|
||||
} else {
|
||||
entries.remove(entry);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given entry from the set (if present).
|
||||
*
|
||||
* @param entry the entry to remove
|
||||
*/
|
||||
public void remove(E entry) {
|
||||
entries.remove(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all entries from the set.
|
||||
*/
|
||||
public void clear() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all entries which have expired from the internal structure.
|
||||
*/
|
||||
public void removeExpiredEntries() {
|
||||
entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of the entry until it expires (provided it is not removed or re-added).
|
||||
* If the entry does not exist, a duration of -1 seconds is returned.
|
||||
*
|
||||
* @param entry the entry whose duration before it expires should be returned
|
||||
* @return duration the entry will remain in the set (if there are not modifications)
|
||||
*/
|
||||
public Duration getExpiration(E entry) {
|
||||
Long expiration = entries.get(entry);
|
||||
if (expiration == null) {
|
||||
return new Duration(-1, TimeUnit.SECONDS);
|
||||
}
|
||||
long stillPresentMillis = expiration - System.currentTimeMillis();
|
||||
if (stillPresentMillis < 0) {
|
||||
entries.remove(entry);
|
||||
return new Duration(-1, TimeUnit.SECONDS);
|
||||
}
|
||||
return Duration.createWithSuitableUnit(stillPresentMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new expiration duration. Note that already present entries
|
||||
* will still make use of the old expiration.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public void setExpiration(long duration, TimeUnit unit) {
|
||||
this.expirationMillis = unit.toMillis(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this map is empty. This reflects the state of the
|
||||
* internal map, which may contain expired entries only. The result
|
||||
* may change after running {@link #removeExpiredEntries()}.
|
||||
*
|
||||
* @return true if map is really empty, false otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package fr.xephi.authme.util.expiring;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Keeps a count per key which expires after a configurable amount of time.
|
||||
* <p>
|
||||
* Once the expiration of an entry has been reached, the counter resets
|
||||
* to 0. The counter returns 0 rather than {@code null} for any given key.
|
||||
*
|
||||
* @param <K> the type of the key
|
||||
*/
|
||||
public class TimedCounter<K> extends ExpiringMap<K, Integer> {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param duration the duration of time after which entries expire
|
||||
* @param unit the time unit in which {@code duration} is expressed
|
||||
*/
|
||||
public TimedCounter(long duration, TimeUnit unit) {
|
||||
super(duration, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(K key) {
|
||||
Integer value = super.get(key);
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the value stored for the provided key.
|
||||
*
|
||||
* @param key the key to increment the counter for
|
||||
*/
|
||||
public void increment(K key) {
|
||||
put(key, get(key) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the value stored for the provided key.
|
||||
* This method will NOT update the expiration.
|
||||
*
|
||||
* @param key the key to increment the counter for
|
||||
*/
|
||||
public void decrement(K key) {
|
||||
ExpiringEntry<Integer> e = getEntries().get(key);
|
||||
|
||||
if (e != null) {
|
||||
if (e.getValue() <= 0) {
|
||||
remove(key);
|
||||
} else {
|
||||
getEntries().put(key, new ExpiringEntry<>(e.getValue() - 1, e.getExpiration()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total of all non-expired entries in this counter.
|
||||
*
|
||||
* @return the total of all valid entries
|
||||
*/
|
||||
public int total() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
return getEntries().values().stream()
|
||||
.filter(entry -> currentTime <= entry.getExpiration())
|
||||
.map(ExpiringEntry::getValue)
|
||||
.reduce(0, Integer::sum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Replaceable tag whose value depends on an argument.
|
||||
*
|
||||
* @param <A> the argument type
|
||||
*/
|
||||
public class DependentTag<A> implements Tag<A> {
|
||||
|
||||
private final String name;
|
||||
private final Function<A, String> replacementFunction;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name the tag (placeholder) that will be replaced
|
||||
* @param replacementFunction the function producing the replacement
|
||||
*/
|
||||
public DependentTag(String name, Function<A, String> replacementFunction) {
|
||||
this.name = name;
|
||||
this.replacementFunction = replacementFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(A argument) {
|
||||
return replacementFunction.apply(argument);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Tag to be replaced that does not depend on an argument.
|
||||
*
|
||||
* @param <A> type of the argument (not used in this implementation)
|
||||
*/
|
||||
public class SimpleTag<A> implements Tag<A> {
|
||||
|
||||
private final String name;
|
||||
private final Supplier<String> replacementFunction;
|
||||
|
||||
public SimpleTag(String name, Supplier<String> replacementFunction) {
|
||||
this.name = name;
|
||||
this.replacementFunction = replacementFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(A argument) {
|
||||
return replacementFunction.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
/**
|
||||
* Represents a tag in a text to be replaced with a value (which may depend on some argument).
|
||||
*
|
||||
* @param <A> argument type the replacement may depend on
|
||||
*/
|
||||
public interface Tag<A> {
|
||||
|
||||
/**
|
||||
* @return the tag to replace
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the value to replace the tag with for the given argument.
|
||||
*
|
||||
* @param argument the argument to evaluate the replacement for
|
||||
* @return the replacement
|
||||
*/
|
||||
String getValue(A argument);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Utility class for creating tags.
|
||||
*/
|
||||
public final class TagBuilder {
|
||||
|
||||
private TagBuilder() {
|
||||
}
|
||||
|
||||
public static <A> Tag<A> createTag(String name, Function<A, String> replacementFunction) {
|
||||
return new DependentTag<>(name, replacementFunction);
|
||||
}
|
||||
|
||||
public static <A> Tag<A> createTag(String name, Supplier<String> replacementFunction) {
|
||||
return new SimpleTag<>(name, replacementFunction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Replaces tags lazily by first determining which tags are being used
|
||||
* and only applying those replacements afterwards.
|
||||
*
|
||||
* @param <A> the argument type
|
||||
*/
|
||||
public final class TagReplacer<A> {
|
||||
|
||||
private final List<Tag<A>> tags;
|
||||
private final Collection<String> messages;
|
||||
|
||||
/**
|
||||
* Private constructor. Use {@link #newReplacer(Collection, Collection)}.
|
||||
*
|
||||
* @param tags the tags that are being used in the messages
|
||||
* @param messages the messages
|
||||
*/
|
||||
private TagReplacer(List<Tag<A>> tags, Collection<String> messages) {
|
||||
this.tags = tags;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class, which will provide the given
|
||||
* messages adapted with the provided tags.
|
||||
*
|
||||
* @param allTags all available tags
|
||||
* @param messages the messages to use
|
||||
* @param <A> the argument type
|
||||
* @return new tag replacer instance
|
||||
*/
|
||||
public static <A> TagReplacer<A> newReplacer(Collection<Tag<A>> allTags, Collection<String> messages) {
|
||||
List<Tag<A>> usedTags = determineUsedTags(allTags, messages);
|
||||
return new TagReplacer<>(usedTags, messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messages with the tags applied for the given argument.
|
||||
*
|
||||
* @param argument the argument to get the messages for
|
||||
* @return the adapted messages
|
||||
*/
|
||||
public List<String> getAdaptedMessages(A argument) {
|
||||
// Note ljacqu 20170121: Using a Map might seem more natural here but we avoid doing so for performance
|
||||
// Although the performance gain here is probably minimal...
|
||||
List<TagValue> tagValues = new LinkedList<>();
|
||||
for (Tag<A> tag : tags) {
|
||||
tagValues.add(new TagValue(tag.getName(), tag.getValue(argument)));
|
||||
}
|
||||
|
||||
List<String> adaptedMessages = new LinkedList<>();
|
||||
for (String line : messages) {
|
||||
String adaptedLine = line;
|
||||
for (TagValue tagValue : tagValues) {
|
||||
adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value);
|
||||
}
|
||||
adaptedMessages.add(adaptedLine);
|
||||
}
|
||||
return adaptedMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which tags are used somewhere in the given list of messages.
|
||||
*
|
||||
* @param allTags all available tags
|
||||
* @param messages the messages
|
||||
* @param <A> argument type
|
||||
* @return tags used at least once
|
||||
*/
|
||||
private static <A> List<Tag<A>> determineUsedTags(Collection<Tag<A>> allTags, Collection<String> messages) {
|
||||
return allTags.stream()
|
||||
.filter(tag -> messages.stream().anyMatch(msg -> msg.contains(tag.getName())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** (Tag, value) pair. */
|
||||
private static final class TagValue {
|
||||
|
||||
/** The tag to replace. */
|
||||
private final String tag;
|
||||
/** The value to replace with. */
|
||||
private final String value;
|
||||
|
||||
TagValue(String tag, String value) {
|
||||
this.tag = tag;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TagValue[tag='" + tag + "', value='" + value + "']";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package fr.xephi.authme.util.lazytags;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Applies tags lazily to the String property of an item. This class wraps
|
||||
* a {@link TagReplacer} with the extraction of the String property and
|
||||
* the creation of new items with the adapted string property.
|
||||
*
|
||||
* @param <T> the item type
|
||||
* @param <A> the argument type to evaluate the replacements
|
||||
*/
|
||||
public class WrappedTagReplacer<T, A> {
|
||||
|
||||
private final Collection<T> items;
|
||||
private final BiFunction<T, String, ? extends T> itemCreator;
|
||||
private final TagReplacer<A> tagReplacer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param allTags all available tags
|
||||
* @param items the items to apply the replacements on
|
||||
* @param stringGetter getter of the String property to adapt on the items
|
||||
* @param itemCreator a function taking (T, String): the original item and the adapted String, returning a new item
|
||||
*/
|
||||
public WrappedTagReplacer(Collection<Tag<A>> allTags,
|
||||
Collection<T> items,
|
||||
Function<? super T, String> stringGetter,
|
||||
BiFunction<T, String, ? extends T> itemCreator) {
|
||||
this.items = items;
|
||||
this.itemCreator = itemCreator;
|
||||
|
||||
List<String> stringItems = items.stream().map(stringGetter).collect(Collectors.toList());
|
||||
tagReplacer = TagReplacer.newReplacer(allTags, stringItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates adapted items for the given argument.
|
||||
*
|
||||
* @param argument the argument to adapt the items for
|
||||
* @return the adapted items
|
||||
*/
|
||||
public List<T> getAdaptedItems(A argument) {
|
||||
List<String> adaptedStrings = tagReplacer.getAdaptedMessages(argument);
|
||||
List<T> adaptedItems = new LinkedList<>();
|
||||
|
||||
Iterator<T> originalItemsIter = items.iterator();
|
||||
Iterator<String> newStringsIter = adaptedStrings.iterator();
|
||||
while (originalItemsIter.hasNext() && newStringsIter.hasNext()) {
|
||||
adaptedItems.add(itemCreator.apply(originalItemsIter.next(), newStringsIter.next()));
|
||||
}
|
||||
return adaptedItems;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user