From 1a2ac2f8dfbd74ccd59f75c5d326228e5a711bac Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Fri, 16 Jan 2015 14:27:47 -0800 Subject: [PATCH] First strike at MCEdit file format Thanks to WorldEdit source code for some of this code. --- .../aufdemrand/denizen/objects/dCuboid.java | 17 +- .../commands/world/SchematicCommand.java | 10 +- .../denizen/utilities/blocks/BlockData.java | 14 + .../utilities/blocks/CuboidBlockSet.java | 251 +++++++++- .../denizen/utilities/jnbt/ByteArrayTag.java | 57 +++ .../denizen/utilities/jnbt/ByteTag.java | 49 ++ .../denizen/utilities/jnbt/CompoundTag.java | 420 +++++++++++++++++ .../utilities/jnbt/CompoundTagBuilder.java | 204 +++++++++ .../denizen/utilities/jnbt/DoubleTag.java | 50 ++ .../denizen/utilities/jnbt/EndTag.java | 37 ++ .../denizen/utilities/jnbt/FloatTag.java | 49 ++ .../denizen/utilities/jnbt/IntArrayTag.java | 60 +++ .../denizen/utilities/jnbt/IntTag.java | 49 ++ .../denizen/utilities/jnbt/ListTag.java | 431 ++++++++++++++++++ .../utilities/jnbt/ListTagBuilder.java | 119 +++++ .../denizen/utilities/jnbt/LongTag.java | 50 ++ .../denizen/utilities/jnbt/NBTConstants.java | 81 ++++ .../utilities/jnbt/NBTInputStream.java | 171 +++++++ .../utilities/jnbt/NBTOutputStream.java | 294 ++++++++++++ .../denizen/utilities/jnbt/NBTUtils.java | 147 ++++++ .../denizen/utilities/jnbt/NamedTag.java | 63 +++ .../denizen/utilities/jnbt/ShortTag.java | 49 ++ .../denizen/utilities/jnbt/StringTag.java | 52 +++ .../denizen/utilities/jnbt/Tag.java | 34 ++ 24 files changed, 2730 insertions(+), 28 deletions(-) create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteArrayTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTagBuilder.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/DoubleTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/EndTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/FloatTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntArrayTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTagBuilder.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/LongTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTConstants.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTInputStream.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTOutputStream.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTUtils.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/NamedTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/ShortTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/StringTag.java create mode 100644 src/main/java/net/aufdemrand/denizen/utilities/jnbt/Tag.java diff --git a/src/main/java/net/aufdemrand/denizen/objects/dCuboid.java b/src/main/java/net/aufdemrand/denizen/objects/dCuboid.java index eb7bf5e679..f0395cdf30 100644 --- a/src/main/java/net/aufdemrand/denizen/objects/dCuboid.java +++ b/src/main/java/net/aufdemrand/denizen/objects/dCuboid.java @@ -428,8 +428,8 @@ public List getBlocks_internal(List materials) { int x_distance = pair.x_distance; for (int x = 0; x != x_distance + 1; x++) { - for (int z = 0; z != z_distance + 1; z++) { - for (int y = 0; y != y_distance + 1; y++) { + for (int y = 0; y != y_distance + 1; y++) { + for (int z = 0; z != z_distance + 1; z++) { loc = new dLocation(loc_1.clone().add(x, y, z)); if (!filter.isEmpty() && loc.getY() >= 0 && loc.getY() < 256) { // Check filter @@ -469,8 +469,8 @@ public void setBlocks_internal(List materials) { int x_distance = pair.x_distance; for (int x = 0; x != x_distance + 1; x++) { - for (int z = 0; z != z_distance + 1; z++) { - for (int y = 0; y != y_distance + 1; y++) { + for (int y = 0; y != y_distance + 1; y++) { + for (int z = 0; z != z_distance + 1; z++) { if (loc_1.getY() + y >= 0 && loc_1.getY() + y < 256) { materials.get(index).setBlock(loc_1.clone().add(x, y, z).getBlock()); } @@ -492,8 +492,8 @@ public BlockData getBlockAt(double nX, double nY, double nZ, List mat int x_distance = pair.x_distance; for (int x = 0; x != x_distance + 1; x++) { - for (int z = 0; z != z_distance + 1; z++) { - for (int y = 0; y != y_distance + 1; y++) { + for (int y = 0; y != y_distance + 1; y++) { + for (int z = 0; z != z_distance + 1; z++) { if (x == nX && nY == y && z == nZ) { return materials.get(index); } @@ -571,9 +571,8 @@ public dList getSpawnableBlocks(List mats) { int x_distance = pair.x_distance; for (int x = 0; x != x_distance + 1; x++) { - for (int z = 0; z != z_distance + 1; z++) { - for (int y = 0; y != y_distance; y++) { - + for (int y = 0; y != y_distance + 1; y++) { + for (int z = 0; z != z_distance + 1; z++) { loc = new dLocation(loc_1.clone() .add(x, y, z)); diff --git a/src/main/java/net/aufdemrand/denizen/scripts/commands/world/SchematicCommand.java b/src/main/java/net/aufdemrand/denizen/scripts/commands/world/SchematicCommand.java index d7644f630a..7318db156a 100644 --- a/src/main/java/net/aufdemrand/denizen/scripts/commands/world/SchematicCommand.java +++ b/src/main/java/net/aufdemrand/denizen/scripts/commands/world/SchematicCommand.java @@ -135,7 +135,8 @@ public void execute(ScriptEntry scriptEntry) throws CommandExecutionException { return; } InputStream fs = new FileInputStream(f); - set = CuboidBlockSet.fromCompressedString(ScriptHelper.convertStreamToString(fs)); + //set = CuboidBlockSet.fromCompressedString(ScriptHelper.convertStreamToString(fs)); + set = CuboidBlockSet.fromMCEditStream(fs); fs.close(); schematics.put(name.asString().toUpperCase(), set); } @@ -191,12 +192,13 @@ public void execute(ScriptEntry scriptEntry) throws CommandExecutionException { set = schematics.get(name.asString().toUpperCase()); String directory = URLDecoder.decode(System.getProperty("user.dir")); File f = new File(directory + "/plugins/Denizen/schematics/" + name.asString() + ".schematic"); - String output = set.toCompressedFormat(); + //String output = set.toCompressedFormat(); FileOutputStream fs = new FileOutputStream(f); - OutputStreamWriter osw = new OutputStreamWriter(fs); + set.saveMCEditFormatToStream(fs); + /*OutputStreamWriter osw = new OutputStreamWriter(fs); osw.write(output); osw.flush(); - osw.close(); + osw.close();*/ fs.flush(); fs.close(); } diff --git a/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockData.java b/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockData.java index dc58966fac..8cf3b91a3e 100644 --- a/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockData.java +++ b/src/main/java/net/aufdemrand/denizen/utilities/blocks/BlockData.java @@ -1,5 +1,6 @@ package net.aufdemrand.denizen.utilities.blocks; +import net.aufdemrand.denizen.utilities.jnbt.CompoundTag; import net.aufdemrand.denizencore.utilities.CoreUtilities; import org.bukkit.Material; import org.bukkit.block.Block; @@ -14,6 +15,11 @@ public class BlockData { public BlockData() { } + public BlockData(short mat, byte dat) { + material = Material.getMaterial(mat); + data = dat; + } + public BlockData(Block block) { material = block.getType(); data = block.getData(); @@ -38,4 +44,12 @@ public static BlockData fromCompressedString(String str) { } return data; } + + public CompoundTag getNBTTag() { + return null; + } + + public void setNBTTag(CompoundTag tag) { + return; + } } diff --git a/src/main/java/net/aufdemrand/denizen/utilities/blocks/CuboidBlockSet.java b/src/main/java/net/aufdemrand/denizen/utilities/blocks/CuboidBlockSet.java index e6d6b9b1a5..10c49c82b3 100644 --- a/src/main/java/net/aufdemrand/denizen/utilities/blocks/CuboidBlockSet.java +++ b/src/main/java/net/aufdemrand/denizen/utilities/blocks/CuboidBlockSet.java @@ -2,11 +2,18 @@ import net.aufdemrand.denizen.objects.dCuboid; import net.aufdemrand.denizen.objects.dLocation; +import net.aufdemrand.denizen.utilities.debugging.dB; import net.aufdemrand.denizencore.utilities.CoreUtilities; +import net.aufdemrand.denizen.utilities.jnbt.*; import org.bukkit.Location; +import org.bukkit.util.*; +import org.bukkit.util.Vector; -import java.util.ArrayList; -import java.util.List; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; public class CuboidBlockSet implements BlockSet { @@ -16,16 +23,18 @@ public CuboidBlockSet() { public CuboidBlockSet(dCuboid cuboid, Location center) { Location low = cuboid.pairs.get(0).low; Location high = cuboid.pairs.get(0).high; - cuboid = new dCuboid(low, high); - List locs = cuboid.getBlocks_internal(null); x_width = high.getX() - low.getX(); y_length = high.getY() - low.getY(); z_height = high.getZ() - low.getZ(); center_x = center.getX() - low.getX(); center_y = center.getY() - low.getY(); center_z = center.getZ() - low.getZ(); - for (int i = 0; i < locs.size(); i++) { - blocks.add(new BlockData(locs.get(i).getBlock())); + for (int x = 0; x < x_width; x++) { + for (int y = 0; y < y_length; y++) { + for (int z = 0; z < z_height; z++) { + blocks.add(new BlockData(low.clone().add(x, y, z).getBlock())); + } + } } } @@ -56,12 +65,21 @@ public dCuboid getCuboid(Location loc) { @Override public void setBlocks(Location loc) { - getCuboid(loc).setBlocks_internal(blocks); + int index = 0; + for (int x = 0; x < x_width; x++) { + for (int y = 0; y < y_length; y++) { + for (int z = 0; z < z_height; z++) { + blocks.get(index).setBlock(loc.clone().add(x, y, z).getBlock()); + index++; + } + } + } } @Override public String toCompressedFormat() { StringBuilder sb = new StringBuilder(blocks.size() * 20); + sb.append("cuboid:"); sb.append(x_width).append(':').append(y_length).append(':').append(z_height).append(':'); sb.append(center_x).append(':').append(center_y).append(':').append(center_z).append('\n'); for (BlockData block: blocks) { @@ -70,20 +88,32 @@ public String toCompressedFormat() { return sb.toString(); } - public BlockData blockAt(double x, double y, double z) { - return getCuboid(new Location(null, 0, 0, 0)).getBlockAt(x, y, z, blocks); + public BlockData blockAt(double X, double Y, double Z) { + // TODO: Calculate instead of this wreck + int index = 0; + for (int x = 0; x < x_width; x++) { + for (int y = 0; y < y_length; y++) { + for (int z = 0; z < z_height; z++) { + if (x == X && y == Y && z == Z) { + return blocks.get(index); + } + index++; + } + } + } + return null; } public static CuboidBlockSet fromCompressedString(String str) { CuboidBlockSet cbs = new CuboidBlockSet(); List split = CoreUtilities.split(str, '\n'); List details = CoreUtilities.split(split.get(0), ':'); - cbs.x_width = Double.parseDouble(details.get(0)); - cbs.y_length = Double.parseDouble(details.get(1)); - cbs.z_height = Double.parseDouble(details.get(2)); - cbs.center_x = Double.parseDouble(details.get(3)); - cbs.center_y = Double.parseDouble(details.get(4)); - cbs.center_z = Double.parseDouble(details.get(5)); + cbs.x_width = Double.parseDouble(details.get(1)); + cbs.y_length = Double.parseDouble(details.get(2)); + cbs.z_height = Double.parseDouble(details.get(3)); + cbs.center_x = Double.parseDouble(details.get(4)); + cbs.center_y = Double.parseDouble(details.get(5)); + cbs.center_z = Double.parseDouble(details.get(6)); split.remove(0); for (String read: split) { if (read.length() > 0) { @@ -92,4 +122,195 @@ public static CuboidBlockSet fromCompressedString(String str) { } return cbs; } + + public static CuboidBlockSet fromMCEditStream(InputStream is) { + CuboidBlockSet cbs = new CuboidBlockSet(); + try { + NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(is)); + NamedTag rootTag = nbtStream.readNamedTag(); + nbtStream.close(); + if (!rootTag.getName().equals("Schematic")) { + throw new Exception("Tag 'Schematic' does not exist or is not first!"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + Map schematic = schematicTag.getValue(); + short width = (getChildTag(schematic, "Width", ShortTag.class).getValue()); + short length = (getChildTag(schematic, "Length", ShortTag.class).getValue()); + short height = (getChildTag(schematic, "Height", ShortTag.class).getValue()); + int originX = 0; + int originY = 0; + int originZ = 0; + try { + originX = getChildTag(schematic, "WEOriginX", IntTag.class).getValue(); + originY = getChildTag(schematic, "WEOriginY", IntTag.class).getValue(); + originZ = getChildTag(schematic, "WEOriginZ", IntTag.class).getValue(); + } + catch (Exception e) { + // Default origin, why not + } + cbs.x_width = width; + cbs.z_height = length; + cbs.y_length = height; + cbs.center_x = originX; + cbs.center_y = originY; + cbs.center_z = originZ; + // Disregard Offset + String materials = getChildTag(schematic, "Materials", StringTag.class).getValue(); + if (!materials.equals("Alpha")) { + throw new Exception("Schematic file is not an Alpha schematic!"); + } + byte[] blockId = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue(); + byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue(); + byte[] addId = new byte[0]; + short[] blocks = new short[blockId.length]; + if (schematic.containsKey("AddBlocks")) { + addId = getChildTag(schematic, "AddBlocks", ByteArrayTag.class).getValue(); + } + for (int index = 0; index < blockId.length; index++) { + if ((index >> 1) >= addId.length) { + blocks[index] = (short)(blockId[index] & 0xFF); + } + else { + if ((index & 1) == 0) { + blocks[index] = (short)(((addId[index >> 1] & 0x0F) << 8) + (blockId[index] & 0xFF)); + } + else { + blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (blockId[index] & 0xFF)); + } + } + } + List tileEntities = getChildTag(schematic, "TileEntities", ListTag.class).getValue(); + Map> tileEntitiesMap = new HashMap>(); + for (Tag tag: tileEntities) { + if (!(tag instanceof CompoundTag)) { + continue; + } + CompoundTag t = (CompoundTag) tag; + int x = 0; + int y = 0; + int z = 0; + Map values = new HashMap(); + for (Map.Entry entry : t.getValue().entrySet()) { + if (entry.getKey().equals("x")) { + if (entry.getValue() instanceof IntTag) { + x = ((IntTag) entry.getValue()).getValue(); + } + } + else if (entry.getKey().equals("y")) { + if (entry.getValue() instanceof IntTag) { + y = ((IntTag) entry.getValue()).getValue(); + } + } + else if (entry.getKey().equals("z")) { + if (entry.getValue() instanceof IntTag) { + z = ((IntTag) entry.getValue()).getValue(); + } + } + values.put(entry.getKey(), entry.getValue()); + } + BlockVector vec = new BlockVector(x, y, z); + tileEntitiesMap.put(vec, values); + } + org.bukkit.util.Vector vec = new Vector(width, height, length); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + for (int z = 0; z < length; z++) { + int index = y * width * length + z * width + x; + BlockVector pt = new BlockVector(x, y, z); + BlockData block = new BlockData(blocks[index], blockData[index]); + if (tileEntitiesMap.containsKey(pt)) { + block.setNBTTag(new CompoundTag(tileEntitiesMap.get(pt))); + } + cbs.blocks.add(block); + } + } + } + } + catch (Exception e) { + dB.echoError(e); + } + return cbs; + } + + private static T getChildTag(Map items, String key, + Class expected) throws Exception { + if (!items.containsKey(key)) { + throw new Exception("Schematic file is missing a '" + key + "' tag"); + } + Tag tag = items.get(key); + if (!expected.isInstance(tag)) { + throw new Exception(key + " tag is not of tag type " + expected.getName()); + } + return expected.cast(tag); + } + + // Thanks to WorldEdit for sample code + public void saveMCEditFormatToStream(OutputStream os) { + try { + HashMap schematic = new HashMap(); + schematic.put("Width", new ShortTag((short) (x_width))); + schematic.put("Length", new ShortTag((short) (z_height))); + schematic.put("Height", new ShortTag((short) (y_length))); + schematic.put("Materials", new StringTag("Alpha")); + schematic.put("WEOriginX", new IntTag((int) center_x)); + schematic.put("WEOriginY", new IntTag((int) center_y)); + schematic.put("WEOriginZ", new IntTag((int) center_z)); + schematic.put("WEOffsetX", new IntTag(0)); + schematic.put("WEOffsetY", new IntTag(0)); + schematic.put("WEOffsetZ", new IntTag(0)); + byte[] blocks = new byte[(int) ((x_width) * (y_length) * (z_height))]; + byte[] addBlocks = null; + byte[] blockData = new byte[blocks.length]; + ArrayList tileEntities = new ArrayList(); + int indexer = 0; + for (int x = 0; x < x_width; x++) { + for (int y = 0; y < y_length; y++) { + for (int z = 0; z < z_height; z++) { + int index = (int) (y * (x_width) * (z_height) + z * (x_width) + x); + BlockData bd = this.blocks.get(indexer);//blockAt(x, y, z); + indexer++; + if (bd.material.getId() > 255) { + if (addBlocks == null) { + addBlocks = new byte[(blocks.length >> 1) + 1]; + } + addBlocks[index >> 1] = (byte) (((index & 1) == 0) ? + addBlocks[index >> 1] & 0xF0 | (bd.material.getId() >> 8) & 0xF + : addBlocks[index >> 1] & 0xF | ((bd.material.getId() >> 8) & 0xF) << 4); + } + blocks[index] = (byte) bd.material.getId(); + blockData[index] = (byte) bd.data; + + CompoundTag rawTag = bd.getNBTTag(); + if (rawTag != null) { + HashMap values = new HashMap(); + for (Map.Entry entry : rawTag.getValue().entrySet()) { + values.put(entry.getKey(), entry.getValue()); // TODO: Why manual copy? + } + values.put("id", new StringTag(null)); // block.getNbtId() + values.put("x", new IntTag(x)); + values.put("y", new IntTag(y)); + values.put("z", new IntTag(z)); + CompoundTag tileEntityTag = new CompoundTag(values); + tileEntities.add(tileEntityTag); + } + } + } + } + schematic.put("Blocks", new ByteArrayTag(blocks)); + schematic.put("Data", new ByteArrayTag(blockData)); + schematic.put("Entities", new ListTag(CompoundTag.class, new ArrayList())); + schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities)); + if (addBlocks != null) { + schematic.put("AddBlocks", new ByteArrayTag(addBlocks)); + } + CompoundTag schematicTag = new CompoundTag(schematic); + NBTOutputStream stream = new NBTOutputStream(new GZIPOutputStream(os)); + stream.writeNamedTag("Schematic", schematicTag); + os.flush(); + stream.close(); + } + catch (Exception e) { + dB.echoError(e); + } + } } diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteArrayTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteArrayTag.java new file mode 100644 index 0000000000..bb0b1bb44b --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteArrayTag.java @@ -0,0 +1,57 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Byte_Array} tag. + */ +public final class ByteArrayTag extends Tag { + + private final byte[] value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public ByteArrayTag(byte[] value) { + super(); + this.value = value; + } + + @Override + public byte[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for (byte b : value) { + String hexDigits = Integer.toHexString(b).toUpperCase(); + if (hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + return "TAG_Byte_Array(" + hex + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteTag.java new file mode 100644 index 0000000000..6deee7a9d4 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ByteTag.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Byte} tag. + */ +public final class ByteTag extends Tag { + + private final byte value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public ByteTag(byte value) { + super(); + this.value = value; + } + + @Override + public Byte getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Byte(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTag.java new file mode 100644 index 0000000000..eb58fbe424 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTag.java @@ -0,0 +1,420 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The {@code TAG_Compound} tag. + */ +public final class CompoundTag extends Tag { + + private final Map value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public CompoundTag(Map value) { + super(); + this.value = Collections.unmodifiableMap(value); + } + + /** + * Returns whether this compound tag contains the given key. + * + * @param key the given key + * @return true if the tag contains the given key + */ + public boolean containsKey(String key) { + return value.containsKey(key); + } + + @Override + public Map getValue() { + return value; + } + + /** + * Return a new compound tag with the given values. + * + * @param value the value + * @return the new compound tag + */ + public CompoundTag setValue(Map value) { + return new CompoundTag(value); + } + + /** + * Create a compound tag builder. + * + * @return the builder + */ + public CompoundTagBuilder createBuilder() { + return new CompoundTagBuilder(new HashMap(value)); + } + + /** + * Get a byte array named with the given key. + * + *

If the key does not exist or its value is not a byte array tag, + * then an empty byte array will be returned.

+ * + * @param key the key + * @return a byte array + */ + public byte[] getByteArray(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteArrayTag) { + return ((ByteArrayTag) tag).getValue(); + } else { + return new byte[0]; + } + } + + /** + * Get a byte named with the given key. + * + *

If the key does not exist or its value is not a byte tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a byte + */ + public byte getByte(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + } else { + return (byte) 0; + } + } + + /** + * Get a double named with the given key. + * + *

If the key does not exist or its value is not a double tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a double + */ + public double getDouble(String key) { + Tag tag = value.get(key); + if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a double named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a double + */ + public double asDouble(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + + } else { + return 0; + } + } + + /** + * Get a float named with the given key. + * + *

If the key does not exist or its value is not a float tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a float + */ + public float getFloat(String key) { + Tag tag = value.get(key); + if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a {@code int[]} named with the given key. + * + *

If the key does not exist or its value is not an int array tag, + * then an empty array will be returned.

+ * + * @param key the key + * @return an int array + */ + public int[] getIntArray(String key) { + Tag tag = value.get(key); + if (tag instanceof IntArrayTag) { + return ((IntArrayTag) tag).getValue(); + } else { + return new int[0]; + } + } + + /** + * Get an int named with the given key. + * + *

If the key does not exist or its value is not an int tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return an int + */ + public int getInt(String key) { + Tag tag = value.get(key); + if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get an int named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return an int + */ + public int asInt(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue().intValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().intValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().intValue(); + + } else { + return 0; + } + } + + /** + * Get a list of tags named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty list will be returned.

+ * + * @param key the key + * @return a list of tags + */ + public List getList(String key) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + return ((ListTag) tag).getValue(); + } else { + return Collections.emptyList(); + } + } + + /** + * Get a {@code TagList} named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty tag list will be returned.

+ * + * @param key the key + * @return a tag list instance + */ + public ListTag getListTag(String key) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + return (ListTag) tag; + } else { + return new ListTag(StringTag.class, Collections.emptyList()); + } + } + + /** + * Get a list of tags named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty list will be returned. If the given key references + * a list but the list of of a different type, then an empty + * list will also be returned.

+ * + * @param key the key + * @param listType the class of the contained type + * @return a list of tags + * @param the type of list + */ + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + } + + /** + * Get a long named with the given key. + * + *

If the key does not exist or its value is not a long tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a long + */ + public long getLong(String key) { + Tag tag = value.get(key); + if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + } else { + return 0L; + } + } + + /** + * Get a long named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a long + */ + public long asLong(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().longValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().longValue(); + + } else { + return 0L; + } + } + + /** + * Get a short named with the given key. + * + *

If the key does not exist or its value is not a short tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a short + */ + public short getShort(String key) { + Tag tag = value.get(key); + if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a string named with the given key. + * + *

If the key does not exist or its value is not a string tag, + * then {@code ""} will be returned.

+ * + * @param key the key + * @return a string + */ + public String getString(String key) { + Tag tag = value.get(key); + if (tag instanceof StringTag) { + return ((StringTag) tag).getValue(); + } else { + return ""; + } + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_Compound").append(": ").append(value.size()).append(" entries\r\n{\r\n"); + for (Map.Entry entry : value.entrySet()) { + bldr.append(" ").append(entry.getValue().toString().replaceAll("\r\n", "\r\n ")).append("\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTagBuilder.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTagBuilder.java new file mode 100644 index 0000000000..d508d7b2ea --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/CompoundTagBuilder.java @@ -0,0 +1,204 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Helps create compound tags. + */ +public class CompoundTagBuilder { + + private final Map entries; + + /** + * Create a new instance. + */ + CompoundTagBuilder() { + this.entries = new HashMap(); + } + + /** + * Create a new instance and use the given map (which will be modified). + * + * @param value the value + */ + CompoundTagBuilder(Map value) { + checkNotNull(value); + this.entries = value; + } + + /** + * Put the given key and tag into the compound tag. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder put(String key, Tag value) { + checkNotNull(key); + checkNotNull(value); + entries.put(key, value); + return this; + } + + /** + * Put the given key and value into the compound tag as a + * {@code ByteArrayTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putByteArray(String key, byte[] value) { + return put(key, new ByteArrayTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code ByteTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putByte(String key, byte value) { + return put(key, new ByteTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code DoubleTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putDouble(String key, double value) { + return put(key, new DoubleTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code FloatTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putFloat(String key, float value) { + return put(key, new FloatTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code IntArrayTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putIntArray(String key, int[] value) { + return put(key, new IntArrayTag(value)); + } + + /** + * Put the given key and value into the compound tag as an {@code IntTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putInt(String key, int value) { + return put(key, new IntTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code LongTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putLong(String key, long value) { + return put(key, new LongTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code ShortTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putShort(String key, short value) { + return put(key, new ShortTag(value)); + } + + /** + * Put the given key and value into the compound tag as a + * {@code StringTag}. + * + * @param key they key + * @param value the value + * @return this object + */ + public CompoundTagBuilder putString(String key, String value) { + return put(key, new StringTag(value)); + } + + /** + * Put all the entries from the given map into this map. + * + * @param value the map of tags + * @return this object + */ + public CompoundTagBuilder putAll(Map value) { + checkNotNull(value); + for (Map.Entry entry : value.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Build an unnamed compound tag with this builder's entries. + * + * @return the new compound tag + */ + public CompoundTag build() { + return new CompoundTag(new HashMap(entries)); + } + + /** + * Create a new builder instance. + * + * @return a new builder + */ + public static CompoundTagBuilder create() { + return new CompoundTagBuilder(); + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/DoubleTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/DoubleTag.java new file mode 100644 index 0000000000..01ac513276 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/DoubleTag.java @@ -0,0 +1,50 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Double} tag. + * + */ +public final class DoubleTag extends Tag { + + private final double value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public DoubleTag(double value) { + super(); + this.value = value; + } + + @Override + public Double getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Double(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/EndTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/EndTag.java new file mode 100644 index 0000000000..2e517e94d4 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/EndTag.java @@ -0,0 +1,37 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_End} tag. + */ +public final class EndTag extends Tag { + + @Override + public Object getValue() { + return null; + } + + @Override + public String toString() { + return "TAG_End"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/FloatTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/FloatTag.java new file mode 100644 index 0000000000..082d45447e --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/FloatTag.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Float} tag. + */ +public final class FloatTag extends Tag { + + private final float value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public FloatTag(float value) { + super(); + this.value = value; + } + + @Override + public Float getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Float(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntArrayTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntArrayTag.java new file mode 100644 index 0000000000..c5e46ebd3c --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntArrayTag.java @@ -0,0 +1,60 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The {@code TAG_Int_Array} tag. + */ +public final class IntArrayTag extends Tag { + + private final int[] value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public IntArrayTag(int[] value) { + super(); + checkNotNull(value); + this.value = value; + } + + @Override + public int[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for (int b : value) { + String hexDigits = Integer.toHexString(b).toUpperCase(); + if (hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + return "TAG_Int_Array(" + hex + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntTag.java new file mode 100644 index 0000000000..213fdb3b76 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/IntTag.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Int} tag. + */ +public final class IntTag extends Tag { + + private final int value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public IntTag(int value) { + super(); + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Int(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTag.java new file mode 100644 index 0000000000..a52125a75c --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTag.java @@ -0,0 +1,431 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The {@code TAG_List} tag. + */ +public final class ListTag extends Tag { + + private final Class type; + private final List value; + + /** + * Creates the tag with an empty name. + * + * @param type the type of tag + * @param value the value of the tag + */ + public ListTag(Class type, List value) { + super(); + checkNotNull(value); + this.type = type; + this.value = Collections.unmodifiableList(value); + } + + /** + * Gets the type of item in this list. + * + * @return The type of item in this list. + */ + public Class getType() { + return type; + } + + @Override + public List getValue() { + return value; + } + + /** + * Create a new list tag with this tag's name and type. + * + * @param list the new list + * @return a new list tag + */ + public ListTag setValue(List list) { + return new ListTag(getType(), list); + } + + /** + * Get the tag if it exists at the given index. + * + * @param index the index + * @return the tag or null + */ + @Nullable + public Tag getIfExists(int index) { + try { + return value.get(index); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get a byte array named with the given index. + * + *

If the index does not exist or its value is not a byte array tag, + * then an empty byte array will be returned.

+ * + * @param index the index + * @return a byte array + */ + public byte[] getByteArray(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteArrayTag) { + return ((ByteArrayTag) tag).getValue(); + } else { + return new byte[0]; + } + } + + /** + * Get a byte named with the given index. + * + *

If the index does not exist or its value is not a byte tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a byte + */ + public byte getByte(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + } else { + return (byte) 0; + } + } + + /** + * Get a double named with the given index. + * + *

If the index does not exist or its value is not a double tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a double + */ + public double getDouble(int index) { + Tag tag = getIfExists(index); + if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a double named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a double + */ + public double asDouble(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + + } else { + return 0; + } + } + + /** + * Get a float named with the given index. + * + *

If the index does not exist or its value is not a float tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a float + */ + public float getFloat(int index) { + Tag tag = getIfExists(index); + if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a {@code int[]} named with the given index. + * + *

If the index does not exist or its value is not an int array tag, + * then an empty array will be returned.

+ * + * @param index the index + * @return an int array + */ + public int[] getIntArray(int index) { + Tag tag = getIfExists(index); + if (tag instanceof IntArrayTag) { + return ((IntArrayTag) tag).getValue(); + } else { + return new int[0]; + } + } + + /** + * Get an int named with the given index. + * + *

If the index does not exist or its value is not an int tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return an int + */ + public int getInt(int index) { + Tag tag = getIfExists(index); + if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get an int named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return an int + */ + public int asInt(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue().intValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().intValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().intValue(); + + } else { + return 0; + } + } + + /** + * Get a list of tags named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty list will be returned.

+ * + * @param index the index + * @return a list of tags + */ + public List getList(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + return ((ListTag) tag).getValue(); + } else { + return Collections.emptyList(); + } + } + + /** + * Get a {@code TagList} named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty tag list will be returned.

+ * + * @param index the index + * @return a tag list instance + */ + public ListTag getListTag(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + return (ListTag) tag; + } else { + return new ListTag(StringTag.class, Collections.emptyList()); + } + } + + /** + * Get a list of tags named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty list will be returned. If the given index references + * a list but the list of of a different type, then an empty + * list will also be returned.

+ * + * @param index the index + * @param listType the class of the contained type + * @return a list of tags + * @param the NBT type + */ + @SuppressWarnings("unchecked") + public List getList(int index, Class listType) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + } + + /** + * Get a long named with the given index. + * + *

If the index does not exist or its value is not a long tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a long + */ + public long getLong(int index) { + Tag tag = getIfExists(index); + if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + } else { + return 0L; + } + } + + /** + * Get a long named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a long + */ + public long asLong(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().longValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().longValue(); + + } else { + return 0; + } + } + + /** + * Get a short named with the given index. + * + *

If the index does not exist or its value is not a short tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a short + */ + public short getShort(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a string named with the given index. + * + *

If the index does not exist or its value is not a string tag, + * then {@code ""} will be returned.

+ * + * @param index the index + * @return a string + */ + public String getString(int index) { + Tag tag = getIfExists(index); + if (tag instanceof StringTag) { + return ((StringTag) tag).getValue(); + } else { + return ""; + } + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_List").append(": ").append(value.size()).append(" entries of type ").append(NBTUtils.getTypeName(type)).append("\r\n{\r\n"); + for (Tag t : value) { + bldr.append(" ").append(t.toString().replaceAll("\r\n", "\r\n ")).append("\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTagBuilder.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTagBuilder.java new file mode 100644 index 0000000000..79191ae57c --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ListTagBuilder.java @@ -0,0 +1,119 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Helps create list tags. + */ +public class ListTagBuilder { + + private final Class type; + private final List entries; + + /** + * Create a new instance. + * + * @param type of tag contained in this list + */ + ListTagBuilder(Class type) { + checkNotNull(type); + this.type = type; + this.entries = new ArrayList(); + } + + /** + * Add the given tag. + * + * @param value the tag + * @return this object + */ + public ListTagBuilder add(Tag value) { + checkNotNull(value); + if (!type.isInstance(value)) { + throw new IllegalArgumentException(value.getClass().getCanonicalName() + " is not of expected type " + type.getCanonicalName()); + } + entries.add(value); + return this; + } + + /** + * Add all the tags in the given list. + * + * @param value a list of tags + * @return this object + */ + public ListTagBuilder addAll(Collection value) { + checkNotNull(value); + for (Tag v : value) { + add(v); + } + return this; + } + + /** + * Build an unnamed list tag with this builder's entries. + * + * @return the new list tag + */ + public ListTag build() { + return new ListTag(type, new ArrayList(entries)); + } + + /** + * Create a new builder instance. + * + * @return a new builder + */ + public static ListTagBuilder create(Class type) { + return new ListTagBuilder(type); + } + + /** + * Create a new builder instance. + * + * @return a new builder + */ + public static ListTagBuilder createWith(T ... entries) { + checkNotNull(entries); + + if (entries.length == 0) { + throw new IllegalArgumentException("This method needs an array of at least one entry"); + } + + Class type = entries[0].getClass(); + for (int i = 1; i < entries.length; i++) { + if (!type.isInstance(entries[i])) { + throw new IllegalArgumentException("An array of different tag types was provided"); + } + } + + ListTagBuilder builder = new ListTagBuilder(type); + builder.addAll(Arrays.asList(entries)); + return builder; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/LongTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/LongTag.java new file mode 100644 index 0000000000..31441a3993 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/LongTag.java @@ -0,0 +1,50 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Long} tag. + * + */ +public final class LongTag extends Tag { + + private final long value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public LongTag(long value) { + super(); + this.value = value; + } + + @Override + public Long getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Long(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTConstants.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTConstants.java new file mode 100644 index 0000000000..570ee6e334 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTConstants.java @@ -0,0 +1,81 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.nio.charset.Charset; + +/** + * A class which holds constant values. + */ +public final class NBTConstants { + + public static final Charset CHARSET = Charset.forName("UTF-8"); + + public static final int TYPE_END = 0, TYPE_BYTE = 1, TYPE_SHORT = 2, + TYPE_INT = 3, TYPE_LONG = 4, TYPE_FLOAT = 5, TYPE_DOUBLE = 6, + TYPE_BYTE_ARRAY = 7, TYPE_STRING = 8, TYPE_LIST = 9, + TYPE_COMPOUND = 10, TYPE_INT_ARRAY = 11; + + /** + * Default private constructor. + */ + private NBTConstants() { + + } + + /** + * Convert a type ID to its corresponding {@link Tag} class. + * + * @param id type ID + * @return tag class + * @throws IllegalArgumentException thrown if the tag ID is not valid + */ + public static Class getClassFromType(int id) { + switch (id) { + case TYPE_END: + return EndTag.class; + case TYPE_BYTE: + return ByteTag.class; + case TYPE_SHORT: + return ShortTag.class; + case TYPE_INT: + return IntTag.class; + case TYPE_LONG: + return LongTag.class; + case TYPE_FLOAT: + return FloatTag.class; + case TYPE_DOUBLE: + return DoubleTag.class; + case TYPE_BYTE_ARRAY: + return ByteArrayTag.class; + case TYPE_STRING: + return StringTag.class; + case TYPE_LIST: + return ListTag.class; + case TYPE_COMPOUND: + return CompoundTag.class; + case TYPE_INT_ARRAY: + return IntArrayTag.class; + default: + throw new IllegalArgumentException("Unknown tag type ID of " + id); + } + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTInputStream.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTInputStream.java new file mode 100644 index 0000000000..2a60efa9f4 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTInputStream.java @@ -0,0 +1,171 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class reads NBT, or Named Binary Tag + * streams, and produces an object graph of subclasses of the {@code Tag} + * object. + * + *

The NBT format was created by Markus Persson, and the specification may be + * found at + * http://www.minecraft.net/docs/NBT.txt.

+ */ +public final class NBTInputStream implements Closeable { + + private final DataInputStream is; + + /** + * Creates a new {@code NBTInputStream}, which will source its data + * from the specified input stream. + * + * @param is the input stream + * @throws IOException if an I/O error occurs + */ + public NBTInputStream(InputStream is) throws IOException { + this.is = new DataInputStream(is); + } + + /** + * Reads an NBT tag from the stream. + * + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + public NamedTag readNamedTag() throws IOException { + return readNamedTag(0); + } + + /** + * Reads an NBT from the stream. + * + * @param depth the depth of this tag + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + private NamedTag readNamedTag(int depth) throws IOException { + int type = is.readByte() & 0xFF; + + String name; + if (type != NBTConstants.TYPE_END) { + int nameLength = is.readShort() & 0xFFFF; + byte[] nameBytes = new byte[nameLength]; + is.readFully(nameBytes); + name = new String(nameBytes, NBTConstants.CHARSET); + } else { + name = ""; + } + + return new NamedTag(name, readTagPayload(type, depth)); + } + + /** + * Reads the payload of a tag given the type. + * + * @param type the type + * @param depth the depth + * @return the tag + * @throws IOException if an I/O error occurs. + */ + private Tag readTagPayload(int type, int depth) throws IOException { + switch (type) { + case NBTConstants.TYPE_END: + if (depth == 0) { + throw new IOException( + "TAG_End found without a TAG_Compound/TAG_List tag preceding it."); + } else { + return new EndTag(); + } + case NBTConstants.TYPE_BYTE: + return new ByteTag(is.readByte()); + case NBTConstants.TYPE_SHORT: + return new ShortTag(is.readShort()); + case NBTConstants.TYPE_INT: + return new IntTag(is.readInt()); + case NBTConstants.TYPE_LONG: + return new LongTag(is.readLong()); + case NBTConstants.TYPE_FLOAT: + return new FloatTag(is.readFloat()); + case NBTConstants.TYPE_DOUBLE: + return new DoubleTag(is.readDouble()); + case NBTConstants.TYPE_BYTE_ARRAY: + int length = is.readInt(); + byte[] bytes = new byte[length]; + is.readFully(bytes); + return new ByteArrayTag(bytes); + case NBTConstants.TYPE_STRING: + length = is.readShort(); + bytes = new byte[length]; + is.readFully(bytes); + return new StringTag(new String(bytes, NBTConstants.CHARSET)); + case NBTConstants.TYPE_LIST: + int childType = is.readByte(); + length = is.readInt(); + + List tagList = new ArrayList(); + for (int i = 0; i < length; ++i) { + Tag tag = readTagPayload(childType, depth + 1); + if (tag instanceof EndTag) { + throw new IOException("TAG_End not permitted in a list."); + } + tagList.add(tag); + } + + return new ListTag(NBTUtils.getTypeClass(childType), tagList); + case NBTConstants.TYPE_COMPOUND: + Map tagMap = new HashMap(); + while (true) { + NamedTag namedTag = readNamedTag(depth + 1); + Tag tag = namedTag.getTag(); + if (tag instanceof EndTag) { + break; + } else { + tagMap.put(namedTag.getName(), tag); + } + } + + return new CompoundTag(tagMap); + case NBTConstants.TYPE_INT_ARRAY: + length = is.readInt(); + int[] data = new int[length]; + for (int i = 0; i < length; i++) { + data[i] = is.readInt(); + } + return new IntArrayTag(data); + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + @Override + public void close() throws IOException { + is.close(); + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTOutputStream.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTOutputStream.java new file mode 100644 index 0000000000..71b03b8a99 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTOutputStream.java @@ -0,0 +1,294 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This class writes NBT, or Named Binary Tag + * {@code Tag} objects to an underlying {@code OutputStream}. + * + *

The NBT format was created by Markus Persson, and the specification may be + * found at + * http://www.minecraft.net/docs/NBT.txt.

+ */ +public final class NBTOutputStream implements Closeable { + + /** + * The output stream. + */ + private final DataOutputStream os; + + /** + * Creates a new {@code NBTOutputStream}, which will write data to the + * specified underlying output stream. + * + * @param os + * The output stream. + * @throws IOException + * if an I/O error occurs. + */ + public NBTOutputStream(OutputStream os) throws IOException { + this.os = new DataOutputStream(os); + } + + /** + * Writes a tag. + * + * @param tag + * The tag to write. + * @throws IOException + * if an I/O error occurs. + */ + public void writeNamedTag(String name, Tag tag) throws IOException { + checkNotNull(name); + checkNotNull(tag); + + int type = NBTUtils.getTypeCode(tag.getClass()); + byte[] nameBytes = name.getBytes(NBTConstants.CHARSET); + + os.writeByte(type); + os.writeShort(nameBytes.length); + os.write(nameBytes); + + if (type == NBTConstants.TYPE_END) { + throw new IOException("Named TAG_End not permitted."); + } + + writeTagPayload(tag); + } + + /** + * Writes tag payload. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeTagPayload(Tag tag) throws IOException { + int type = NBTUtils.getTypeCode(tag.getClass()); + switch (type) { + case NBTConstants.TYPE_END: + writeEndTagPayload((EndTag) tag); + break; + case NBTConstants.TYPE_BYTE: + writeByteTagPayload((ByteTag) tag); + break; + case NBTConstants.TYPE_SHORT: + writeShortTagPayload((ShortTag) tag); + break; + case NBTConstants.TYPE_INT: + writeIntTagPayload((IntTag) tag); + break; + case NBTConstants.TYPE_LONG: + writeLongTagPayload((LongTag) tag); + break; + case NBTConstants.TYPE_FLOAT: + writeFloatTagPayload((FloatTag) tag); + break; + case NBTConstants.TYPE_DOUBLE: + writeDoubleTagPayload((DoubleTag) tag); + break; + case NBTConstants.TYPE_BYTE_ARRAY: + writeByteArrayTagPayload((ByteArrayTag) tag); + break; + case NBTConstants.TYPE_STRING: + writeStringTagPayload((StringTag) tag); + break; + case NBTConstants.TYPE_LIST: + writeListTagPayload((ListTag) tag); + break; + case NBTConstants.TYPE_COMPOUND: + writeCompoundTagPayload((CompoundTag) tag); + break; + case NBTConstants.TYPE_INT_ARRAY: + writeIntArrayTagPayload((IntArrayTag) tag); + break; + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + /** + * Writes a {@code TAG_Byte} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeByteTagPayload(ByteTag tag) throws IOException { + os.writeByte(tag.getValue()); + } + + /** + * Writes a {@code TAG_Byte_Array} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException { + byte[] bytes = tag.getValue(); + os.writeInt(bytes.length); + os.write(bytes); + } + + /** + * Writes a {@code TAG_Compound} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeCompoundTagPayload(CompoundTag tag) throws IOException { + for (Map.Entry entry : tag.getValue().entrySet()) { + writeNamedTag(entry.getKey(), entry.getValue()); + } + os.writeByte((byte) 0); // end tag - better way? + } + + /** + * Writes a {@code TAG_List} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeListTagPayload(ListTag tag) throws IOException { + Class clazz = tag.getType(); + List tags = tag.getValue(); + int size = tags.size(); + + os.writeByte(NBTUtils.getTypeCode(clazz)); + os.writeInt(size); + for (Tag tag1 : tags) { + writeTagPayload(tag1); + } + } + + /** + * Writes a {@code TAG_String} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeStringTagPayload(StringTag tag) throws IOException { + byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET); + os.writeShort(bytes.length); + os.write(bytes); + } + + /** + * Writes a {@code TAG_Double} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeDoubleTagPayload(DoubleTag tag) throws IOException { + os.writeDouble(tag.getValue()); + } + + /** + * Writes a {@code TAG_Float} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeFloatTagPayload(FloatTag tag) throws IOException { + os.writeFloat(tag.getValue()); + } + + /** + * Writes a {@code TAG_Long} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeLongTagPayload(LongTag tag) throws IOException { + os.writeLong(tag.getValue()); + } + + /** + * Writes a {@code TAG_Int} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeIntTagPayload(IntTag tag) throws IOException { + os.writeInt(tag.getValue()); + } + + /** + * Writes a {@code TAG_Short} tag. + * + * @param tag + * The tag. + * @throws IOException + * if an I/O error occurs. + */ + private void writeShortTagPayload(ShortTag tag) throws IOException { + os.writeShort(tag.getValue()); + } + + /** + * Writes a {@code TAG_Empty} tag. + * + * @param tag the tag + */ + private void writeEndTagPayload(EndTag tag) { + /* empty */ + } + + private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException { + int[] data = tag.getValue(); + os.writeInt(data.length); + for (int aData : data) { + os.writeInt(aData); + } + } + + @Override + public void close() throws IOException { + os.close(); + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTUtils.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTUtils.java new file mode 100644 index 0000000000..4ae86a14e9 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NBTUtils.java @@ -0,0 +1,147 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * A class which contains NBT-related utility methods. + * + */ +public final class NBTUtils { + + /** + * Default private constructor. + */ + private NBTUtils() { + } + + /** + * Gets the type name of a tag. + * + * @param clazz the tag class + * @return The type name. + */ + public static String getTypeName(Class clazz) { + if (clazz.equals(ByteArrayTag.class)) { + return "TAG_Byte_Array"; + } else if (clazz.equals(ByteTag.class)) { + return "TAG_Byte"; + } else if (clazz.equals(CompoundTag.class)) { + return "TAG_Compound"; + } else if (clazz.equals(DoubleTag.class)) { + return "TAG_Double"; + } else if (clazz.equals(EndTag.class)) { + return "TAG_End"; + } else if (clazz.equals(FloatTag.class)) { + return "TAG_Float"; + } else if (clazz.equals(IntTag.class)) { + return "TAG_Int"; + } else if (clazz.equals(ListTag.class)) { + return "TAG_List"; + } else if (clazz.equals(LongTag.class)) { + return "TAG_Long"; + } else if (clazz.equals(ShortTag.class)) { + return "TAG_Short"; + } else if (clazz.equals(StringTag.class)) { + return "TAG_String"; + } else if (clazz.equals(IntArrayTag.class)) { + return "TAG_Int_Array"; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + + clazz.getName() + ")."); + } + } + + /** + * Gets the type code of a tag class. + * + * @param clazz the tag class + * @return The type code. + * @throws IllegalArgumentException if the tag class is invalid. + */ + public static int getTypeCode(Class clazz) { + if (clazz.equals(ByteArrayTag.class)) { + return NBTConstants.TYPE_BYTE_ARRAY; + } else if (clazz.equals(ByteTag.class)) { + return NBTConstants.TYPE_BYTE; + } else if (clazz.equals(CompoundTag.class)) { + return NBTConstants.TYPE_COMPOUND; + } else if (clazz.equals(DoubleTag.class)) { + return NBTConstants.TYPE_DOUBLE; + } else if (clazz.equals(EndTag.class)) { + return NBTConstants.TYPE_END; + } else if (clazz.equals(FloatTag.class)) { + return NBTConstants.TYPE_FLOAT; + } else if (clazz.equals(IntTag.class)) { + return NBTConstants.TYPE_INT; + } else if (clazz.equals(ListTag.class)) { + return NBTConstants.TYPE_LIST; + } else if (clazz.equals(LongTag.class)) { + return NBTConstants.TYPE_LONG; + } else if (clazz.equals(ShortTag.class)) { + return NBTConstants.TYPE_SHORT; + } else if (clazz.equals(StringTag.class)) { + return NBTConstants.TYPE_STRING; + } else if (clazz.equals(IntArrayTag.class)) { + return NBTConstants.TYPE_INT_ARRAY; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + + clazz.getName() + ")."); + } + } + + /** + * Gets the class of a type of tag. + * + * @param type the type + * @return The class. + * @throws IllegalArgumentException if the tag type is invalid. + */ + public static Class getTypeClass(int type) { + switch (type) { + case NBTConstants.TYPE_END: + return EndTag.class; + case NBTConstants.TYPE_BYTE: + return ByteTag.class; + case NBTConstants.TYPE_SHORT: + return ShortTag.class; + case NBTConstants.TYPE_INT: + return IntTag.class; + case NBTConstants.TYPE_LONG: + return LongTag.class; + case NBTConstants.TYPE_FLOAT: + return FloatTag.class; + case NBTConstants.TYPE_DOUBLE: + return DoubleTag.class; + case NBTConstants.TYPE_BYTE_ARRAY: + return ByteArrayTag.class; + case NBTConstants.TYPE_STRING: + return StringTag.class; + case NBTConstants.TYPE_LIST: + return ListTag.class; + case NBTConstants.TYPE_COMPOUND: + return CompoundTag.class; + case NBTConstants.TYPE_INT_ARRAY: + return IntArrayTag.class; + default: + throw new IllegalArgumentException("Invalid tag type : " + type + + "."); + } + } +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NamedTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NamedTag.java new file mode 100644 index 0000000000..42e5582c95 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/NamedTag.java @@ -0,0 +1,63 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A tag that has a name. + */ +public class NamedTag { + + private final String name; + private final Tag tag; + + /** + * Create a new named tag. + * + * @param name the name + * @param tag the tag + */ + public NamedTag(String name, Tag tag) { + checkNotNull(name); + checkNotNull(tag); + this.name = name; + this.tag = tag; + } + + /** + * Get the name of the tag. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Get the tag. + * + * @return the tag + */ + public Tag getTag() { + return tag; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ShortTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ShortTag.java new file mode 100644 index 0000000000..4c010c20ef --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/ShortTag.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * The {@code TAG_Short} tag. + */ +public final class ShortTag extends Tag { + + private final short value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public ShortTag(short value) { + super(); + this.value = value; + } + + @Override + public Short getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_Short(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/StringTag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/StringTag.java new file mode 100644 index 0000000000..326c8a7678 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/StringTag.java @@ -0,0 +1,52 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The {@code TAG_String} tag. + */ +public final class StringTag extends Tag { + + private final String value; + + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public StringTag(String value) { + super(); + checkNotNull(value); + this.value = value; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + return "TAG_String(" + value + ")"; + } + +} diff --git a/src/main/java/net/aufdemrand/denizen/utilities/jnbt/Tag.java b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/Tag.java new file mode 100644 index 0000000000..1458aeda51 --- /dev/null +++ b/src/main/java/net/aufdemrand/denizen/utilities/jnbt/Tag.java @@ -0,0 +1,34 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.aufdemrand.denizen.utilities.jnbt; + +/** + * Represents a NBT tag. + */ +public abstract class Tag { + + /** + * Gets the value of this tag. + * + * @return the value + */ + public abstract Object getValue(); + +}