diff --git a/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java index 6007d44..97b56e6 100644 --- a/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java +++ b/src/main/java/me/youhavetrouble/blockedit/BlockEdit.java @@ -3,6 +3,7 @@ package me.youhavetrouble.blockedit; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.stream.JsonReader; +import me.youhavetrouble.blockedit.schematic.Schematic; import me.youhavetrouble.blockedit.wands.SelectionWand; import org.bukkit.plugin.java.JavaPlugin; import org.reflections.Reflections; @@ -15,7 +16,7 @@ import java.util.regex.Pattern; public final class BlockEdit extends JavaPlugin { private static BlockEdit plugin; - private static SchematicHandler schematicHandler; + private static SchematicHandler schematicHandler; private static WandsHandler wandsHandler; @Override @@ -30,7 +31,7 @@ public final class BlockEdit extends JavaPlugin { getServer().getPluginManager().registerEvents(new JoinLeaveListener(), this); - schematicHandler = new SchematicHandler(this); + schematicHandler = new SchematicHandler<>(this); wandsHandler = new WandsHandler(this); SelectionWand selectionWand = new SelectionWand(); @@ -44,7 +45,7 @@ public final class BlockEdit extends JavaPlugin { return plugin; } - public static SchematicHandler getSchematicHandler() { + public static SchematicHandler getSchematicHandler() { return schematicHandler; } diff --git a/src/main/java/me/youhavetrouble/blockedit/SchematicHandler.java b/src/main/java/me/youhavetrouble/blockedit/SchematicHandler.java index a27a88d..9243866 100644 --- a/src/main/java/me/youhavetrouble/blockedit/SchematicHandler.java +++ b/src/main/java/me/youhavetrouble/blockedit/SchematicHandler.java @@ -1,51 +1,79 @@ package me.youhavetrouble.blockedit; +import me.youhavetrouble.blockedit.schematic.Schematic; +import me.youhavetrouble.blockedit.schematic.SchematicProvider; import me.youhavetrouble.blockedit.util.Clipboard; -import me.youhavetrouble.blockedit.util.Schematic; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.*; +import java.util.HashMap; +import java.util.Map; -public class SchematicHandler { +public class SchematicHandler { private final BlockEdit plugin; + private final Map> schematicProvidersByExtension = new HashMap<>(); + private final Map> schematicProvidersByName = new HashMap<>(); + + private final File schematicsDirectory; + protected SchematicHandler(BlockEdit plugin) { this.plugin = plugin; + this.schematicsDirectory = new File(plugin.getDataFolder(), "schematics"); createSchematicsDirectory(); } private void createSchematicsDirectory() { try { - if (!plugin.getDataFolder().exists()) { - plugin.getDataFolder().mkdir(); - } - File schematicsDir = new File(plugin.getDataFolder(), "schematics"); - if (!schematicsDir.exists()) { - schematicsDir.mkdir(); - } + this.schematicsDirectory.mkdirs(); } catch (SecurityException e) { plugin.getLogger().warning("Could not create schematics directory. Make sure server has read/write access to the plugin folder."); } + } + public void registerSchematicProvider( + @NotNull SchematicProvider schematicProvider + ) throws IllegalArgumentException { + if (!schematicProvider.name().matches("^[a-z0-9]+$")) { + throw new IllegalArgumentException("Schematic provider name can only contain lowercase letters and numbers"); + } + + if (schematicProvidersByName.containsKey(schematicProvider.name())) { + throw new IllegalArgumentException("Schematic provider " + schematicProvider.name() + " is already registered"); + } + + // Loop extensions to verify if they're valid and not already registered + for (String extension : schematicProvider.fileExtensions()) { + String trimmedExtension = extension.trim(); + if (trimmedExtension.isEmpty()) throw new IllegalArgumentException("File extension cannot be empty"); + if (!trimmedExtension.matches("^[a-z0-9]+$")) throw new IllegalArgumentException("File extension can only contain lowercase letters and numbers"); + if (schematicProvidersByExtension.containsKey(trimmedExtension)) { + throw new IllegalArgumentException("File extension " + trimmedExtension + " is already registered to " + schematicProvidersByExtension.get(trimmedExtension).name()); + } + } + + // Loop again to actually register the extensions + for (String extension : schematicProvider.fileExtensions()) { + String trimmedExtension = extension.trim(); + schematicProvidersByExtension.put(trimmedExtension, schematicProvider); + } + schematicProvidersByName.put(schematicProvider.name(), schematicProvider); } /** * Loads a schematic from the schematics directory + * @param providerName Schematic provider to use * @param schematicName Name of the schematic - * @return Clipboard object containing the schematic. Null if schematic does not exist + * @return Clipboard object containing the schematic. Null if schematic does not exist or could not be loaded. */ - public Clipboard loadSchematic(String schematicName) { - File schematicFile = new File(plugin.getDataFolder(), "schematics/" + schematicName + ".schematic"); - if (!schematicFile.exists()) { - schematicFile = new File(plugin.getDataFolder(), "schematics/" + schematicName + ".schem"); + public @Nullable Schematic loadSchematic(String providerName, @NotNull String schematicName) { + SchematicProvider schematicProvider = schematicProvidersByName.get(providerName); + if (schematicProvider == null) { + throw new IllegalArgumentException("Schematic provider " + providerName + " is not registered"); } - if (!schematicFile.exists()) { - return null; - } - Schematic schematic = new Schematic(schematicFile); - - - return null; + return schematicProvider.load(schematicName); } /** @@ -53,10 +81,19 @@ public class SchematicHandler { * @param schematicName Name of the schematic * @param clipboard Clipboard object containing the schematic */ - public void saveSchematic(String schematicName, Clipboard clipboard) { + public void saveSchematic( + @NotNull String schematicName, + @NotNull String providerName, + @NotNull Clipboard clipboard + ) { + SchematicProvider schematicProvider = schematicProvidersByName.get(providerName); + if (schematicProvider == null) { + throw new IllegalArgumentException("Schematic provider " + providerName + " is not registered"); + } + S schematic = schematicProvider.fromClipboard(schematicName, clipboard); + + schematicProvider.save(schematic); } - - } diff --git a/src/main/java/me/youhavetrouble/blockedit/api/BlockEditAPI.java b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditAPI.java index 1ec01d6..d544b68 100644 --- a/src/main/java/me/youhavetrouble/blockedit/api/BlockEditAPI.java +++ b/src/main/java/me/youhavetrouble/blockedit/api/BlockEditAPI.java @@ -3,6 +3,7 @@ package me.youhavetrouble.blockedit.api; import me.youhavetrouble.blockedit.BlockEdit; import me.youhavetrouble.blockedit.SchematicHandler; import me.youhavetrouble.blockedit.WandsHandler; +import me.youhavetrouble.blockedit.schematic.Schematic; import me.youhavetrouble.blockedit.util.ChunkWork; import me.youhavetrouble.blockedit.util.Selection; import org.jetbrains.annotations.NotNull; @@ -38,7 +39,8 @@ public class BlockEditAPI { * Gets schematic handler object that can be used to work with schematics * @return Schematic handler */ - public static SchematicHandler getSchematicHandler() { + public static SchematicHandler getSchematicHandler() { return BlockEdit.getSchematicHandler(); } + } diff --git a/src/main/java/me/youhavetrouble/blockedit/schematic/Schematic.java b/src/main/java/me/youhavetrouble/blockedit/schematic/Schematic.java new file mode 100644 index 0000000..e1bbd11 --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/schematic/Schematic.java @@ -0,0 +1,16 @@ +package me.youhavetrouble.blockedit.schematic; + +import me.youhavetrouble.blockedit.util.Clipboard; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +public abstract class Schematic { + + /** + * Get the schematic as a Clipboard object + * @param originLocation The location to use as the origin for the clipboard + * @return The clipboard object containing the schematic + */ + public abstract @NotNull Clipboard asClipboard(@NotNull Location originLocation); + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/schematic/SchematicProvider.java b/src/main/java/me/youhavetrouble/blockedit/schematic/SchematicProvider.java new file mode 100644 index 0000000..d25ab2b --- /dev/null +++ b/src/main/java/me/youhavetrouble/blockedit/schematic/SchematicProvider.java @@ -0,0 +1,44 @@ +package me.youhavetrouble.blockedit.schematic; + +import me.youhavetrouble.blockedit.util.Clipboard; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface for schematic providers. Schematic providers are used to load and save schematics in different formats. + */ +public interface SchematicProvider { + + /** + * Get the name of the schematic provider. Can only contain lowercase letters and numbers. + * @return The name of the schematic provider + */ + @NotNull String name(); + + /** + * Get the file extensions of the schematic provider. + */ + @NotNull String[] fileExtensions(); + + /** + * Save the schematic + * @param schematic The schematic to save + */ + void save(@NotNull S schematic); + + /** + * Load a schematic + * @param name Name of the schematic to load + * @return The loaded schematic. Returns null if the schematic could not be loaded. + */ + @Nullable S load(@NotNull String name); + + /** + * Create a new schematic from the given clipboard + * @param name Name of the schematic + * @param clipboard Clipboard object containing the schematic + * @return The created schematic + */ + @NotNull S fromClipboard(@NotNull String name, @NotNull Clipboard clipboard); + +} diff --git a/src/main/java/me/youhavetrouble/blockedit/util/Schematic.java b/src/main/java/me/youhavetrouble/blockedit/util/Schematic.java deleted file mode 100644 index c2c1c40..0000000 --- a/src/main/java/me/youhavetrouble/blockedit/util/Schematic.java +++ /dev/null @@ -1,71 +0,0 @@ -package me.youhavetrouble.blockedit.util; - -import me.youhavetrouble.blockedit.commands.arguments.BlockDataArgument; -import me.youhavetrouble.blockedit.exception.SchematicLoadException; -import net.querz.nbt.io.NBTInputStream; -import net.querz.nbt.io.NamedTag; -import net.querz.nbt.tag.CompoundTag; -import net.querz.nbt.tag.Tag; -import org.bukkit.block.data.BlockData; - -import java.io.*; -import java.util.List; -import java.util.zip.GZIPInputStream; - -public class Schematic { - - private final short width, height, length; - private final int bitsPerBlock; - private final byte[] blocks; - - private BlockData[] blockPalette = new BlockData[0]; - - public Schematic(File file) throws SchematicLoadException { - try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new FileInputStream(file))))) { - NamedTag tag = new NBTInputStream(dis).readTag(Tag.DEFAULT_MAX_DEPTH); - - if (tag == null) throw new SchematicLoadException("Could not load schematic. Invalid file format"); - if (!(tag.getTag() instanceof CompoundTag compoundTag)) throw new SchematicLoadException("Could not load schematic. Invalid file format"); - - this.width = compoundTag.getShort("Width"); - this.height = compoundTag.getShort("Height"); - this.length = compoundTag.getShort("Length"); - - CompoundTag paletteMap = compoundTag.getCompoundTag("Palette"); - this.blocks = compoundTag.getByteArray("BlockData"); - this.bitsPerBlock = blocks.length >> 6; - - if (paletteMap.size() > 0) { - this.blockPalette = new BlockData[paletteMap.size()]; - } - - for (String key : paletteMap.keySet()) { - int index = paletteMap.getInt(key); - String[] split = key.split(":"); - if (split.length == 2) { - key = split[1]; - } - this.blockPalette[index] = BlockDataArgument.getBlockData(key); - } - - } catch (IOException e) { - throw new SchematicLoadException("Could not load schematic due to I/O error"); - } - } - - public short getWidth() { - return width; - } - - public short getHeight() { - return height; - } - - public short getLength() { - return length; - } - - public List getBlockPalette() { - return List.of(blockPalette); - } -}