Java tutorial
/* * This file is part of Sponge, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.common.world; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; import org.apache.commons.io.FileUtils; import org.spongepowered.common.SpongeImpl; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; /** * Used to migrate Worlds from Bukkit -> Sponge */ public class WorldMigrator { /** * Gets the old world container used when this server used to be running Bukkit. * * @return The world container */ public static Path getOldWorldContainer() { Path worldContainer = Paths.get("."); final Path bukkitYml = worldContainer.resolve("bukkit.yml"); if (Files.exists(bukkitYml)) { try { final ConfigurationNode node = YAMLConfigurationLoader.builder().setPath(bukkitYml).build().load(); final String containerCandidate = node.getNode("settings", "world-container").getString(""); if (!containerCandidate.isEmpty()) { try { worldContainer = Paths.get(worldContainer.toString()).resolve(containerCandidate); } catch (InvalidPathException ipe) { SpongeImpl .getLogger().warn( "Cannot use path [{}] specified under [world-container] in bukkit" + ".yml. Will use [{}] instead.", containerCandidate, worldContainer, ipe); } } } catch (IOException ioe) { SpongeImpl.getLogger().warn("Cannot load bukkit.yml. Will use [{}] instead.", worldContainer, ioe); } } return worldContainer; } /** * Performs the migration of worlds from {@link WorldMigrator#getOldWorldContainer()} to the provided worldContainer. * * @param worldContainer The container to move worlds to */ public static void migrateWorldsTo(Path worldContainer) { SpongeImpl.getLogger().info("Checking for worlds that need to be migrated..."); final Path oldWorldContainer = getOldWorldContainer(); final List<Path> migrated = new ArrayList<>(); if (Files.notExists(oldWorldContainer)) { return; } try (DirectoryStream<Path> stream = Files.newDirectoryStream(oldWorldContainer, entry -> !entry.getFileName().equals(worldContainer.getFileName()) && Files.exists(entry.resolve("level.dat")) && !Files.exists(entry.resolve("level_sponge.dat")))) { for (Path oldWorldPath : stream) { Path worldPath = worldContainer.resolve(oldWorldPath.getFileName()); // Only copy over the old world files if we don't have it already. if (Files.notExists(worldPath)) { SpongeImpl.getLogger().info("Migrating [{}] from [{}].", oldWorldPath.getFileName(), oldWorldContainer); try { worldPath = renameToVanillaNetherOrEnd(worldContainer, oldWorldPath, worldPath); FileUtils.copyDirectory(oldWorldPath.toFile(), worldPath.toFile()); fixInnerNetherOrEndRegionData(worldPath); removeInnerNameFolder(worldPath); migrated.add(worldPath); } catch (IOException ioe) { SpongeImpl.getLogger().warn("Failed to migrate [{}] from [{}] to [{}]", oldWorldPath.getFileName(), oldWorldContainer, worldPath, ioe); } SpongeImpl.getLogger().info("Migrated world [{}] from [{}] to [{}]", oldWorldPath.getFileName(), oldWorldContainer, worldPath); } } } catch (Exception ignore) { } if (migrated.size() > 0) { SpongeImpl.getLogger().info("[{}] worlds have been migrated back to Vanilla's format.", migrated.size()); } else { SpongeImpl.getLogger().info("No worlds were found in need of migration."); } } /** * Determines if a Bukkit nether or end is valid for the server's world container. * * If the server's world name is "world" and we are being given a "world_nether" and "world_the_end", we need to rename these to * "DIM-1/DIM1" (if possible). * * @param worldContainer The world container * @param oldWorldPath The old path to the incoming world * @return True to rename, false to not do so */ private static boolean isValidBukkitNetherOrEnd(Path worldContainer, Path oldWorldPath) { return oldWorldPath.getFileName().toString().startsWith(worldContainer.getFileName().toString()) && (oldWorldPath.getFileName().toString().endsWith("_nether") || oldWorldPath.getFileName().toString().endsWith("_the_end")); } /** * Gets the name we should rename the incoming world folder to. Either "DIM-1" or "DIM1". * * @param worldContainer The old world container where our world is coming from * @param oldWorldPath The old world path * @return The rename or the same name otherwise */ private static String getVanillaNetherOrEndName(Path worldContainer, Path oldWorldPath) { String newName = oldWorldPath.getFileName().toString(); final String[] split = oldWorldPath.getFileName().toString().split(worldContainer.getFileName().toString()); if (split.length > 1) { if (split[1].equals("_nether")) { newName = "DIM-1"; } else if (split[1].equals("_the_end")) { newName = "DIM1"; } } return newName; } /** * Renames the world at the provided path to the proper Vanilla naming for Nether and The_End, if needed. * * Generally this is DIM-1 and DIM1 for Nether and The_End respectively. * * @param worldPath The path to rename * @return The corrected path or the original path if un-needed */ private static Path renameToVanillaNetherOrEnd(Path worldContainer, Path oldWorldPath, Path worldPath) { final String newName = getVanillaNetherOrEndName(worldContainer, oldWorldPath); final Path newWorldPath = worldContainer.resolve(newName); // We only rename the Nether/The_End folder if the prefix matches the worldContainer directory name // Ex. If worldContainer directory name is "world" and folder names "world_nether" or "world_end" exist, we need to rename // those to the correct "DIM-1" and "DIM1" naming respectively. if (isValidBukkitNetherOrEnd(worldContainer, oldWorldPath)) { // If the new world container has no DIM-1/DIM1, we want to move these world files to those names if (Files.notExists(worldContainer.resolve(newName))) { return newWorldPath; } } // Either the new world container has a DIM-1/DIM1 or this is another plugin added world in Bukkit. Either way, just move it over. return worldPath; } /** * Fix Bukkit's desire to have a folder name the same as Vanilla's dimension name INSIDE this folder's name. * * ex. world_nether/DIM-1 world_the_end/DIM1 * * @param oldWorldPath The path to the old world data */ private static void fixInnerNetherOrEndRegionData(Path oldWorldPath) { try { // Copy region within DIM-1 to world root com.google.common.io.Files.move(oldWorldPath.resolve("DIM-1").resolve("region").toFile(), oldWorldPath.resolve("region").toFile()); } catch (IOException ignore) { } try { // Copy region within DIM1 to world root com.google.common.io.Files.move(oldWorldPath.resolve("DIM1").resolve("region").toFile(), oldWorldPath.resolve("region").toFile()); } catch (IOException ignore) { } } /** * The last step of migration involves cleaning up folders within the world folders that match Vanilla dimension names (DIM-1/DIM1). * * @param worldPath The path to inspect */ private static void removeInnerNameFolder(Path worldPath) { // We successfully copied the region folder elsewhere so now delete the DIM-1/DIM1 inside try { Files.delete(worldPath.resolve("DIM-1")); } catch (IOException ignore) { } try { Files.delete(worldPath.resolve("DIM1")); } catch (IOException ignore) { } } }