From d7851b923e797f37cf53459765cbff6e2206e307 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 25 Dec 2018 23:17:06 -0800 Subject: [PATCH] Fixes reload of locales from addons This was a bigger job than expected. I moved the addon loading locale stuff into the LocalesManager class from the AddonsManager and put a jar file lister in Utils. There could be some more combining of plugin jar and addon jar file finding there. Finally, I added a sophisticated test that creates a temporary addon jar with a locale file and checks that it is saved correctly. Phew! --- .../bentobox/managers/AddonsManager.java | 43 ++----- .../bentobox/managers/LocalesManager.java | 31 ++++- .../bentobox/managers/SchemsManager.java | 5 +- .../world/bentobox/bentobox/util/Util.java | 30 +++++ .../bentobox/managers/LocalesManagerTest.java | 112 ++++++++++++++++-- 5 files changed, 173 insertions(+), 48 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java index 2a3c24777..d358a1bbc 100644 --- a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -33,7 +32,6 @@ import world.bentobox.bentobox.api.events.addon.AddonEvent; */ public class AddonsManager { - private static final String LOCALE_FOLDER = "locales"; private List addons; private Map loaders; private final Map> classes = new HashMap<>(); @@ -125,26 +123,30 @@ public class AddonsManager { // try loading the addon // Get description in the addon.yml file YamlConfiguration data = addonDescription(jar); + // Load the addon AddonClassLoader addonClassLoader = new AddonClassLoader(this, data, f, this.getClass().getClassLoader()); + // Get the addon itself addon = addonClassLoader.getAddon(); + // Initialize some settings addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName())); addon.setFile(f); - File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); - // Obtain any locale files and save them - for (String localeFile : listJarFiles(jar, LOCALE_FOLDER, ".yml")) { - addon.saveResource(localeFile, localeDir, false, true); - } + // Locales + plugin.getLocalesManager().copyLocalesFromAddonJar(addon); plugin.getLocalesManager().loadLocalesFromFile(addon.getDescription().getName()); + // Fire the load event Bukkit.getPluginManager().callEvent(new AddonEvent().builder().addon(addon).reason(AddonEvent.Reason.LOAD).build()); + // Add it to the list of addons addons.add(addon); + // Add to the list of loaders loaders.put(addon, addonClassLoader); + // Run the onLoad. addon.onLoad(); } catch (Exception e) { @@ -205,33 +207,6 @@ public class AddonsManager { classes.putIfAbsent(name, clazz); } - /** - * Lists files found in the jar in the folderPath with the suffix given - * @param jar - the jar file - * @param folderPath - the path within the jar - * @param suffix - the suffix required - * @return a list of files - */ - public List listJarFiles(JarFile jar, String folderPath, String suffix) { - List result = new ArrayList<>(); - - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String path = entry.getName(); - - if (!path.startsWith(folderPath)) { - continue; - } - - if (entry.getName().endsWith(suffix)) { - result.add(entry.getName()); - } - - } - return result; - } - private void sortAddons() { // Lists all available addons as names. List names = addons.stream().map(a -> a.getDescription().getName()).collect(Collectors.toList()); diff --git a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java index cedd58bdc..530dd91f7 100644 --- a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java @@ -11,13 +11,16 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.jar.JarFile; import org.bukkit.configuration.file.YamlConfiguration; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.localization.BentoBoxLocale; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.FileLister; +import world.bentobox.bentobox.util.Util; /** * @author tastybento, Poslovitch @@ -31,7 +34,7 @@ public class LocalesManager { public LocalesManager(BentoBox plugin) { this.plugin = plugin; - copyLocalesFromJar(BENTOBOX); + copyLocalesFromPluginJar(BENTOBOX); loadLocalesFromFile(BENTOBOX); // Default } @@ -101,12 +104,29 @@ public class LocalesManager { return result == null ? defaultText : result; } + /** + * Copies locale files from the addon jar to the file system + * @param addon - addon + */ + void copyLocalesFromAddonJar(Addon addon) { + try (JarFile jar = new JarFile(addon.getFile())) { + File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + addon.getDescription().getName()); + if (!localeDir.exists()) { + localeDir.mkdirs(); + // Obtain any locale files and save them + Util.listJarFiles(jar, LOCALE_FOLDER, ".yml").forEach(lf -> addon.saveResource(lf, localeDir, false, true)); + } + } catch (Exception e) { + plugin.logError(e.getMessage()); + } + } + /** * Copies all the locale files from the plugin jar to the filesystem. * Only done if the locale folder does not already exist. * @param folderName - the name of the destination folder */ - private void copyLocalesFromJar(String folderName) { + private void copyLocalesFromPluginJar(String folderName) { // Run through the files and store the locales File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + folderName); // If the folder does not exist, then make it and fill with the locale files from the jar @@ -210,9 +230,12 @@ public class LocalesManager { */ public void reloadLanguages() { languages.clear(); - copyLocalesFromJar(BENTOBOX); + copyLocalesFromPluginJar(BENTOBOX); loadLocalesFromFile(BENTOBOX); - plugin.getAddonsManager().getAddons().forEach(addon -> loadLocalesFromFile(addon.getDescription().getName())); + plugin.getAddonsManager().getAddons().forEach(addon -> { + copyLocalesFromAddonJar(addon); + loadLocalesFromFile(addon.getDescription().getName()); + }); } diff --git a/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java b/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java index 280dad5ae..3287571c8 100644 --- a/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/SchemsManager.java @@ -16,6 +16,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.schems.Clipboard; +import world.bentobox.bentobox.util.Util; public class SchemsManager { @@ -42,9 +43,7 @@ public class SchemsManager { } // Save any schems that try (JarFile jar = new JarFile(addon.getFile())) { - plugin.getAddonsManager().listJarFiles(jar, "schems", ".schem").forEach(name -> { - addon.saveResource(name, false); - }); + Util.listJarFiles(jar, "schems", ".schem").forEach(name -> addon.saveResource(name, false)); } catch (IOException e) { plugin.logError("Could not load schem files from addon jar " + e.getMessage()); } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index 91847adc5..2a44b82cc 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -1,7 +1,10 @@ package world.bentobox.bentobox.util; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.stream.Collectors; import org.bukkit.Bukkit; @@ -195,6 +198,33 @@ public class Util { return world.getEnvironment().equals(Environment.NORMAL) ? world : Bukkit.getWorld(world.getName().replaceAll(NETHER, "").replaceAll(THE_END, "")); } + /** + * Lists files found in the jar in the folderPath with the suffix given + * @param jar - the jar file + * @param folderPath - the path within the jar + * @param suffix - the suffix required + * @return a list of files + */ + public static List listJarFiles(JarFile jar, String folderPath, String suffix) { + List result = new ArrayList<>(); + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String path = entry.getName(); + + if (!path.startsWith(folderPath)) { + continue; + } + + if (entry.getName().endsWith(suffix)) { + result.add(entry.getName()); + } + + } + return result; + } + /** * Converts block face direction to radial degrees. Returns 0 if block face * is not radial. diff --git a/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java index aae3bb2e2..12562f100 100644 --- a/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/LocalesManagerTest.java @@ -9,13 +9,20 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; @@ -79,7 +86,7 @@ public class LocalesManagerTest { */ @After public void cleanUp() throws Exception { - + // Delete locale folder File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER); if (localeDir.exists()) { // Remove it @@ -90,6 +97,16 @@ public class LocalesManagerTest { } + // Delete addon folder + localeDir = new File(plugin.getDataFolder(), "addons"); + if (localeDir.exists()) { + // Remove it + Files.walk(localeDir.toPath()) + .map(Path::toFile) + .sorted((o1, o2) -> -o1.compareTo(o2)) + .forEach(File::delete); + + } } /** @@ -123,7 +140,7 @@ public class LocalesManagerTest { LocalesManager lm = new LocalesManager(plugin); assertNull(lm.get("test.test.test")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(java.lang.String, java.lang.String)}. * @throws IOException @@ -170,7 +187,7 @@ public class LocalesManagerTest { when(user.getLocale()).thenReturn(Locale.US); assertEquals("test string", lm.get(user, "test.test")); } - + /** * Test method for {@link world.bentobox.bentobox.managers.LocalesManager#getOrDefault(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException @@ -283,17 +300,98 @@ public class LocalesManagerTest { List none = new ArrayList<>(); Addon addon = mock(Addon.class); AddonDescription desc = new AddonDescription(); - desc.setName(BENTOBOX); + desc.setName("AcidIsland"); when(addon.getDescription()).thenReturn(desc); + // Create a tmp folder to jar up + File localeDir = new File(LOCALE_FOLDER); + localeDir.mkdirs(); + // Create a fake locale file for this jar + File english = new File(localeDir, Locale.US.toLanguageTag() + ".yml"); + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("test.test", "test string"); + yaml.save(english); + // Create a temporary jar file + File jar = new File("addons", "AcidIsland.jar"); + jar.getParentFile().mkdirs(); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + JarOutputStream target = new JarOutputStream(new FileOutputStream("addons" + File.separator + "AcidIsland.jar"), manifest); + add(english, target); + target.close(); + // When the file is requested, return it + when(addon.getFile()).thenReturn(jar); none.add(addon); when(am.getAddons()).thenReturn(none); when(plugin.getAddonsManager()).thenReturn(am); makeFakeLocaleFile(); LocalesManager lm = new LocalesManager(plugin); + + // RELOAD!!! lm.reloadLanguages(); - Mockito.verify(addon).getDescription(); - File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER + File.separator + BENTOBOX); - assertTrue(localeDir.exists()); + + // Verify that the resources have been saved (note that they are not actually saved because addon is a mock) + Mockito.verify(addon).saveResource( + Mockito.eq("locales/en-US.yml"), + Mockito.any(), + Mockito.eq(false), + Mockito.eq(true) + ); + + // Clean up + // Delete the temp folder we made. Other clean up is done globally + localeDir = new File(plugin.getDataFolder(), "AcidIsland"); + if (localeDir.exists()) { + // Remove it + Files.walk(localeDir.toPath()) + .map(Path::toFile) + .sorted((o1, o2) -> -o1.compareTo(o2)) + .forEach(File::delete); + } + + } + + private void add(File source, JarOutputStream target) throws IOException + { + BufferedInputStream in = null; + try + { + if (source.isDirectory()) + { + String name = source.getPath().replace("\\", "/"); + if (!name.isEmpty()) + { + if (!name.endsWith("/")) + name += "/"; + JarEntry entry = new JarEntry(name); + entry.setTime(source.lastModified()); + target.putNextEntry(entry); + target.closeEntry(); + } + for (File nestedFile: source.listFiles()) + add(nestedFile, target); + return; + } + + JarEntry entry = new JarEntry(source.getPath().replace("\\", "/")); + entry.setTime(source.lastModified()); + target.putNextEntry(entry); + in = new BufferedInputStream(new FileInputStream(source)); + + byte[] buffer = new byte[1024]; + while (true) + { + int count = in.read(buffer); + if (count == -1) + break; + target.write(buffer, 0, count); + } + target.closeEntry(); + } + finally + { + if (in != null) + in.close(); + } } /**