From 811bdee12831c8f3ae6173d064ad40a82b43ba5b Mon Sep 17 00:00:00 2001 From: ljacqu Date: Sat, 21 Jan 2017 13:53:11 +0100 Subject: [PATCH] #1015 Implement lazy replace of tags for welcome message - Check which tags are used when loading the welcome message and only apply their replacements afterwards - Moves AuthMe#replaceAllInfo to a more appropriate place - Avoid fetching data again for each line --- src/main/java/fr/xephi/authme/AuthMe.java | 26 +--- .../process/login/ProcessSyncPlayerLogin.java | 15 +- .../fr/xephi/authme/settings/Settings.java | 11 -- .../settings/WelcomeMessageConfiguration.java | 143 ++++++++++++++++++ .../fr/xephi/authme/util/lazytags/Tag.java | 53 +++++++ .../xephi/authme/settings/SettingsTest.java | 24 --- .../WelcomeMessageConfigurationTest.java | 106 +++++++++++++ 7 files changed, 311 insertions(+), 67 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java create mode 100644 src/main/java/fr/xephi/authme/util/lazytags/Tag.java create mode 100644 src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 74a986543..1b987080b 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -27,7 +27,6 @@ import fr.xephi.authme.permission.PermissionsSystemType; import fr.xephi.authme.security.crypts.SHA256; import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; -import fr.xephi.authme.service.GeoIpService; import fr.xephi.authme.service.MigrationService; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.PluginSettings; @@ -35,11 +34,9 @@ import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.purge.PurgeService; -import fr.xephi.authme.util.PlayerUtils; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; @@ -72,8 +69,6 @@ public class AuthMe extends JavaPlugin { private DataSource database; private BukkitService bukkitService; private Injector injector; - private GeoIpService geoIpService; - private PlayerCache playerCache; /** * Constructor. @@ -242,14 +237,13 @@ public class AuthMe extends JavaPlugin { */ protected void instantiateServices(Injector injector) { // PlayerCache is still injected statically sometimes - playerCache = PlayerCache.getInstance(); + PlayerCache playerCache = PlayerCache.getInstance(); injector.register(PlayerCache.class, playerCache); database = injector.getSingleton(DataSource.class); permsMan = injector.getSingleton(PermissionsManager.class); bukkitService = injector.getSingleton(BukkitService.class); commandHandler = injector.getSingleton(CommandHandler.class); - geoIpService = injector.getSingleton(GeoIpService.class); // Trigger construction of API classes; they will keep track of the singleton injector.getSingleton(NewAPI.class); @@ -344,24 +338,6 @@ public class AuthMe extends JavaPlugin { ConsoleLogger.close(); } - public String replaceAllInfo(String message, Player player) { - String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size()); - String ipAddress = PlayerUtils.getPlayerIp(player); - Server server = getServer(); - return message - .replace("&", "\u00a7") - .replace("{PLAYER}", player.getName()) - .replace("{ONLINE}", playersOnline) - .replace("{MAXPLAYERS}", Integer.toString(server.getMaxPlayers())) - .replace("{IP}", ipAddress) - .replace("{LOGINS}", Integer.toString(playerCache.getLogged())) - .replace("{WORLD}", player.getWorld().getName()) - .replace("{SERVER}", server.getServerName()) - .replace("{VERSION}", server.getBukkitVersion()) - // TODO: We should cache info like this, maybe with a class that extends Player? - .replace("{COUNTRY}", geoIpService.getCountryName(ipAddress)); - } - /** * Handle Bukkit commands. * diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java index d64bda776..6fb5fa8b3 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -13,15 +13,16 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BungeeService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.settings.WelcomeMessageConfiguration; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.util.StringUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.potion.PotionEffectType; import javax.inject.Inject; +import java.util.List; import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; @@ -54,6 +55,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private Settings settings; + @Inject + private WelcomeMessageConfiguration welcomeMessageConfiguration; + ProcessSyncPlayerLogin() { } @@ -103,15 +107,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { player.saveData(); // Login is done, display welcome message + List welcomeMessage = welcomeMessageConfiguration.getWelcomeMessage(player); if (settings.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { if (settings.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) { - for (String s : settings.getWelcomeMessage()) { - Bukkit.getServer().broadcastMessage(plugin.replaceAllInfo(s, player)); - } + welcomeMessage.forEach(bukkitService::broadcastMessage); } else { - for (String s : settings.getWelcomeMessage()) { - player.sendMessage(plugin.replaceAllInfo(s, player)); - } + welcomeMessage.forEach(player::sendMessage); } } diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index d287e140c..980f2adde 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -19,7 +19,6 @@ import static fr.xephi.authme.util.FileUtils.copyFileFromResource; public class Settings extends SettingsManager { private final File pluginFolder; - private String[] welcomeMessage; private String passwordEmailMessage; private String recoveryCodeEmailMessage; @@ -56,19 +55,9 @@ public class Settings extends SettingsManager { return recoveryCodeEmailMessage; } - /** - * Return the lines to output after an in-game registration. - * - * @return The welcome message - */ - public String[] getWelcomeMessage() { - return welcomeMessage; - } - private void loadSettingsFromFiles() { passwordEmailMessage = readFile("email.html"); recoveryCodeEmailMessage = readFile("recovery_code_email.html"); - welcomeMessage = readFile("welcome.txt").split("\\n"); } @Override diff --git a/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java new file mode 100644 index 000000000..b2cc9812e --- /dev/null +++ b/src/main/java/fr/xephi/authme/settings/WelcomeMessageConfiguration.java @@ -0,0 +1,143 @@ +package fr.xephi.authme.settings; + +import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.initialization.Reloadable; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; +import fr.xephi.authme.util.PlayerUtils; +import fr.xephi.authme.util.lazytags.Tag; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import static fr.xephi.authme.util.FileUtils.copyFileFromResource; + +/** + * Configuration for the welcome message (welcome.txt). + */ +public class WelcomeMessageConfiguration implements Reloadable { + + @DataFolder + @Inject + private File pluginFolder; + + @Inject + private Server server; + + @Inject + private GeoIpService geoIpService; + + @Inject + private BukkitService bukkitService; + + @Inject + private PlayerCache playerCache; + + /** List of all supported tags for the welcome message. */ + private final List availableTags = Arrays.asList( + new Tag("&", () -> "\u00a7"), + new Tag("{PLAYER}", pl -> pl.getName()), + new Tag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())), + new Tag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())), + new Tag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)), + new Tag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())), + new Tag("{WORLD}", pl -> pl.getWorld().getName()), + new Tag("{SERVER}", () -> server.getServerName()), + new Tag("{VERSION}", () -> server.getBukkitVersion()), + new Tag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl)))); + + /** Welcome message, by lines. */ + private List welcomeMessage; + /** Tags used in the welcome message. */ + private List usedTags; + + @PostConstruct + @Override + public void reload() { + welcomeMessage = readWelcomeFile(); + usedTags = determineUsedTags(welcomeMessage); + } + + /** + * Returns the welcome message for the given player. + * + * @param player the player for whom the welcome message should be prepared + * @return the welcome message + */ + public List getWelcomeMessage(Player player) { + // 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 tagValues = new LinkedList<>(); + for (Tag tag : usedTags) { + tagValues.add(new TagValue(tag.getName(), tag.getValue(player))); + } + + List adaptedMessages = new LinkedList<>(); + for (String line : welcomeMessage) { + String adaptedLine = line; + for (TagValue tagValue : tagValues) { + adaptedLine = adaptedLine.replace(tagValue.tag, tagValue.value); + } + adaptedMessages.add(adaptedLine); + } + return adaptedMessages; + } + + /** + * @return the lines of the welcome message file + */ + private List readWelcomeFile() { + File welcomeFile = new File(pluginFolder, "welcome.txt"); + if (copyFileFromResource(welcomeFile, "welcome.txt")) { + try { + return Files.readAllLines(welcomeFile.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + ConsoleLogger.logException("Failed to read welcome.txt file:", e); + } + } else { + ConsoleLogger.warning("Failed to copy welcome.txt from JAR"); + } + return Collections.emptyList(); + } + + /** + * Determines which tags are used in the message. + * + * @param welcomeMessage the lines of the welcome message + * @return the tags + */ + private List determineUsedTags(List welcomeMessage) { + return availableTags.stream() + .filter(tag -> welcomeMessage.stream().anyMatch(msg -> msg.contains(tag.getName()))) + .collect(Collectors.toList()); + } + + private static final class TagValue { + + private final String tag; + 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 + "']"; + } + } +} diff --git a/src/main/java/fr/xephi/authme/util/lazytags/Tag.java b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java new file mode 100644 index 000000000..4489f7a7c --- /dev/null +++ b/src/main/java/fr/xephi/authme/util/lazytags/Tag.java @@ -0,0 +1,53 @@ +package fr.xephi.authme.util.lazytags; + +import org.bukkit.entity.Player; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Represents a tag in a text that can be replaced with data (which may depend on the Player). + */ +public class Tag { + + private final String name; + private final Function replacementFunction; + + /** + * Constructor. + * + * @param name the tag (placeholder) that will be replaced + * @param replacementFunction the function producing the replacement + */ + public Tag(String name, Function replacementFunction) { + this.name = name; + this.replacementFunction = replacementFunction; + } + + /** + * Constructor. + * + * @param name the tag (placeholder) that will be replaced + * @param replacementFunction supplier providing the text to replace the tag with + */ + public Tag(String name, Supplier replacementFunction) { + this(name, p -> replacementFunction.get()); + } + + /** + * @return the tag + */ + public String getName() { + return name; + } + + /** + * Returns the value to replace the tag with for the given player. + * + * @param player the player to evaluate the replacement for + * @return the replacement + */ + public String getValue(Player player) { + return replacementFunction.apply(player); + } +} diff --git a/src/test/java/fr/xephi/authme/settings/SettingsTest.java b/src/test/java/fr/xephi/authme/settings/SettingsTest.java index 42326771e..f7d2604c2 100644 --- a/src/test/java/fr/xephi/authme/settings/SettingsTest.java +++ b/src/test/java/fr/xephi/authme/settings/SettingsTest.java @@ -4,7 +4,6 @@ import ch.jalu.configme.configurationdata.ConfigurationData; import ch.jalu.configme.configurationdata.ConfigurationDataBuilder; import ch.jalu.configme.resource.PropertyResource; import fr.xephi.authme.TestHelper; -import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.TestConfiguration; import org.junit.Before; import org.junit.BeforeClass; @@ -16,11 +15,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** @@ -45,26 +41,6 @@ public class SettingsTest { testPluginFolder = temporaryFolder.newFolder(); } - @Test - public void shouldLoadWelcomeMessage() throws IOException { - // given - String welcomeMessage = "This is my welcome message for testing\nBye!"; - File welcomeFile = new File(testPluginFolder, "welcome.txt"); - createFile(welcomeFile); - Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); - - PropertyResource resource = mock(PropertyResource.class); - given(resource.getBoolean(RegistrationSettings.USE_WELCOME_MESSAGE.getPath())).willReturn(true); - Settings settings = new Settings(testPluginFolder, resource, null, CONFIG_DATA); - - // when - String[] result = settings.getWelcomeMessage(); - - // then - assertThat(result, arrayWithSize(2)); - assertThat(result, arrayContaining(welcomeMessage.split("\\n"))); - } - @Test public void shouldLoadEmailMessage() throws IOException { // given diff --git a/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java new file mode 100644 index 000000000..010761a06 --- /dev/null +++ b/src/test/java/fr/xephi/authme/settings/WelcomeMessageConfigurationTest.java @@ -0,0 +1,106 @@ +package fr.xephi.authme.settings; + +import ch.jalu.injector.testing.BeforeInjecting; +import ch.jalu.injector.testing.DelayedInjectionRunner; +import ch.jalu.injector.testing.InjectDelayed; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.data.auth.PlayerCache; +import fr.xephi.authme.initialization.DataFolder; +import fr.xephi.authme.service.BukkitService; +import fr.xephi.authme.service.GeoIpService; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link WelcomeMessageConfiguration}. + */ +@RunWith(DelayedInjectionRunner.class) +public class WelcomeMessageConfigurationTest { + + @InjectDelayed + private WelcomeMessageConfiguration welcomeMessageConfiguration; + @Mock + private Server server; + @Mock + private BukkitService bukkitService; + @Mock + private GeoIpService geoIpService; + @Mock + private PlayerCache playerCache; + @DataFolder + private File testPluginFolder; + + private File welcomeFile; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeInjecting + public void createPluginFolder() throws IOException { + testPluginFolder = temporaryFolder.newFolder(); + welcomeFile = new File(testPluginFolder, "welcome.txt"); + welcomeFile.createNewFile(); + } + + @Test + public void shouldLoadWelcomeMessage() throws IOException { + // given + String welcomeMessage = "This is my welcome message for testing\nBye!"; + Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + Player player = mock(Player.class); + welcomeMessageConfiguration.reload(); + + // when + List result = welcomeMessageConfiguration.getWelcomeMessage(player); + + // then + assertThat(result, hasSize(2)); + assertThat(result, contains(welcomeMessage.split("\\n"))); + verifyZeroInteractions(player, playerCache, geoIpService, bukkitService, server); + } + + @Test + public void shouldReplaceNameAndIpAndCountry() throws IOException { + // given + String welcomeMessage = "Hello {PLAYER}, your IP is {IP}\nYour country is {COUNTRY}.\nWelcome to {SERVER}!"; + Files.write(welcomeFile.toPath(), welcomeMessage.getBytes()); + welcomeMessageConfiguration.reload(); + + Player player = mock(Player.class); + given(player.getName()).willReturn("Bobby"); + TestHelper.mockPlayerIp(player, "123.45.66.77"); + given(geoIpService.getCountryName("123.45.66.77")).willReturn("Syldavia"); + given(server.getServerName()).willReturn("CrazyServer"); + + // when + List result = welcomeMessageConfiguration.getWelcomeMessage(player); + + // then + assertThat(result, hasSize(3)); + assertThat(result.get(0), equalTo("Hello Bobby, your IP is 123.45.66.77")); + assertThat(result.get(1), equalTo("Your country is Syldavia.")); + assertThat(result.get(2), equalTo("Welcome to CrazyServer!")); + verify(server, only()).getServerName(); + verifyZeroInteractions(playerCache); + } +}