diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b69915f --- /dev/null +++ b/.gitignore @@ -0,0 +1,116 @@ +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties diff --git a/AttributeStorage/.classpath b/AttributeStorage/.classpath deleted file mode 100644 index fd7ad7f..0000000 --- a/AttributeStorage/.classpath +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AttributeStorage/.project b/AttributeStorage/.project deleted file mode 100644 index 779621f..0000000 --- a/AttributeStorage/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - AttributeStorage - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/AttributeStorage/.settings/org.eclipse.core.resources.prefs b/AttributeStorage/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index f9fe345..0000000 --- a/AttributeStorage/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/test/java=UTF-8 -encoding/=UTF-8 diff --git a/AttributeStorage/.settings/org.eclipse.jdt.core.prefs b/AttributeStorage/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 60105c1..0000000 --- a/AttributeStorage/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/AttributeStorage/.settings/org.eclipse.m2e.core.prefs b/AttributeStorage/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/AttributeStorage/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/AttributeStorage/pom.xml b/AttributeStorage/pom.xml deleted file mode 100644 index 603dc18..0000000 --- a/AttributeStorage/pom.xml +++ /dev/null @@ -1,142 +0,0 @@ - - 4.0.0 - - com.comphenix.attribute - AttributeStorage - 0.0.2-SNAPSHOT - jar - - AttributeStorage - http://maven.apache.org - - - - comphenix-releases - Comphenix Maven Releases - http://repo.comphenix.net/content/repositories/releases/ - - - comphenix-snapshots - Comphenix Maven Snapshots - http://repo.comphenix.net/content/repositories/snapshots/ - - - - - - bukkit-rep - http://repo.bukkit.org/content/groups/public - - - - - UTF-8 - 1.5 - - - - - - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2 - - - attach-sources - - jar - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9 - - - attach-javadocs - - jar - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.4 - - - sign-artifacts - verify - - sign - - - - - - - - - - - - - org.bukkit - craftbukkit - 1.7.8-R0.1-SNAPSHOT - provided - - - junit - junit - 4.11 - test - - - org.mockito - mockito-all - 1.8.4 - test - - - org.powermock - powermock-module-junit4 - ${powermock.version} - test - - - org.powermock - powermock-api-mockito - ${powermock.version} - test - - - diff --git a/AttributeStorage/src/test/java/com/comphenix/attribute/NbtFactoryTest.java b/AttributeStorage/src/test/java/com/comphenix/attribute/NbtFactoryTest.java deleted file mode 100644 index cfa7a76..0000000 --- a/AttributeStorage/src/test/java/com/comphenix/attribute/NbtFactoryTest.java +++ /dev/null @@ -1,124 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015 Kristian Stangeland -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, -// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -// Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -package com.comphenix.attribute; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; - -import net.minecraft.server.v1_7_R3.NBTTagCompound; -import org.bukkit.craftbukkit.v1_7_R3.inventory.CraftItemFactory; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PrepareForTest; - -import com.comphenix.attribute.NbtFactory.NbtCompound; -import com.comphenix.attribute.NbtFactory.StreamOptions; -import com.comphenix.attribute.mocking.BukkitInitialization; -import com.google.common.io.ByteStreams; -import com.google.common.io.OutputSupplier; - -/** - * Unit test for simple App. - */ -@RunWith(org.powermock.modules.junit4.PowerMockRunner.class) -@PrepareForTest(CraftItemFactory.class) -public class NbtFactoryTest { - @BeforeClass - public static void initializeMeta() throws IllegalAccessException { - BukkitInitialization.initializeItemMeta(); - } - - @Test - public void testCompound() { - NbtCompound compound = createTestCompound(); - - // Simple test - verifyCompound(compound); - } - - private void verifyCompound(NbtCompound compound) { - // Verify the NBT content - assertEquals(NBTTagCompound.class, compound.getHandle().getClass()); - assertEquals(2009, (int)compound.getInteger("released", 0)); - assertEquals("Minecraft", compound.getString("game", "")); - assertEquals(Arrays.asList(1, 2, 3), compound.getList("list", false)); - assertEquals("Markus Persson", compound.getPath("author.name")); - assertEquals("Kristian Stangeland", compound.getPath("fan.name")); - - assertNull("Missing root path was not NULL", compound.getPath("missing.test")); - } - - private NbtCompound createTestCompound() { - // Use NbtFactory.fromCompound(obj); to load from a NBTCompound class. - NbtCompound compound = NbtFactory.createCompound(); - NbtCompound author = NbtFactory.createCompound(); - - compound.put("released", 2009); - compound.put("game", "Minecraft"); - compound.put("author", author); - compound.put("bytes", new byte[] { 1, 2, 3 }); - compound.put("integers", new int[] { 1, 2, 3}); - compound.put("list", NbtFactory.createList(1, 2, 3)); - - author.put("name", "Markus Persson"); - compound.putPath("fan.name", "Kristian Stangeland"); - return compound; - } - - @Test - public void testSaving() throws IOException { - final ByteArrayOutputStream output = new ByteArrayOutputStream(); - final NbtCompound compound = createTestCompound(); - - // Save the compound with compression - compound.saveTo(new OutputSupplier() { - public OutputStream getOutput() throws IOException { - return output; - } - }, StreamOptions.GZIP_COMPRESSION); - - // Load the compound - NbtCompound loaded = NbtFactory.fromStream( - ByteStreams.newInputStreamSupplier(output.toByteArray()), StreamOptions.GZIP_COMPRESSION); - verifyCompound(loaded); - } - - @Test - public void testItemMeta() { - ItemStack stack = NbtFactory.getCraftItemStack(new ItemStack(Material.GOLD_AXE)); - NbtCompound other = NbtFactory.fromItemTag(stack); - - // Do whatever - other.putPath("display.Name", "New display"); - other.putPath("display.Lore", NbtFactory.createList("Line 1", "Line 2")); - - ItemMeta meta = stack.getItemMeta(); - assertEquals("New display", meta.getDisplayName()); - assertEquals(Arrays.asList("Line 1", "Line 2"), meta.getLore()); - } -} diff --git a/AttributeStorage/src/test/java/com/comphenix/attribute/mocking/BukkitInitialization.java b/AttributeStorage/src/test/java/com/comphenix/attribute/mocking/BukkitInitialization.java deleted file mode 100644 index 17cccce..0000000 --- a/AttributeStorage/src/test/java/com/comphenix/attribute/mocking/BukkitInitialization.java +++ /dev/null @@ -1,91 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015 Kristian Stangeland -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, -// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -// Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -package com.comphenix.attribute.mocking; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Field; - -import net.minecraft.server.v1_7_R3.Block; -import net.minecraft.server.v1_7_R3.Item; -import net.minecraft.server.v1_7_R3.StatisticList; - -// Will have to be updated for every version though -import org.bukkit.craftbukkit.v1_7_R3.inventory.CraftItemFactory; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; - -/** - * Used to ensure that ProtocolLib and Bukkit is prepared to be tested. - * - * @author Kristian - */ -public class BukkitInitialization { - private static boolean initialized; - - /** - * Initialize Bukkit and ProtocolLib such that we can perfrom unit testing. - * @throws IllegalAccessException If we are unable to initialize Bukkit. - */ - public static void initializeItemMeta() throws IllegalAccessException { - if (!initialized) { - // Denote that we're done - initialized = true; - - try { - Block.p(); - Item.l(); - StatisticList.a(); - } catch (Exception e) { - // Swallow - e.printStackTrace(); - } - - // Mock the server object - Server mockedServer = mock(Server.class); - ItemFactory mockedFactory = mock(CraftItemFactory.class); - ItemMeta mockedMeta = mock(ItemMeta.class); - - when(mockedServer.getItemFactory()).thenReturn(mockedFactory); - when(mockedServer.isPrimaryThread()).thenReturn(true); - when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta); - - // Inject this fake server - setStaticField(Bukkit.class, "server", mockedServer); - - // And the fake item factory - setStaticField(CraftItemFactory.class, "instance", mockedFactory); - } - } - - private static void setStaticField(Class parent, String name, Object value) { - try { - Field field = parent.getDeclaredField(name); - field.setAccessible(true); - field.set(null, value); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot set static field " + name + ".", e); - } - } -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f086329 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Kristian Stangeland + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1a6a2d4..fa72cc4 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ # AttributeStorage -Able to edit ItemStack attributes in Bukkit 1.7.2 +This library allows your plugin to edit ItemStack attributes in Bukkit/Spigot 1.7 - 1.8 -#License -The MIT License (MIT) - -Copyright (c) 2015 Kristian Stangeland +You can use this as maven dependency: +Repository: +```XML + + comphenix-snapshots + Comphenix Maven Snapshots + http://repo.comphenix.net/content/repositories/snapshots/ + +``` -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Dependency: +```XML + + com.comphenix.attribute + AttributeStorage + 0.0.3-SNAPSHOT + compile + +``` -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +#License +This project is under the the MIT License (MIT), look at the LICENSE file for more informations. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1849096 --- /dev/null +++ b/pom.xml @@ -0,0 +1,51 @@ + + 4.0.0 + + com.comphenix.attribute + AttributeStorage + 0.0.3-SNAPSHOT + jar + + AttributeStorage + https://github.com/aadnk/AttributeStorage + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + UTF-8 + + + + + + maven-compiler-plugin + 3.6.2 + + 1.8 + 1.8 + + + + + + + + org.spigotmc + spigot-api + 1.12.1-R0.1-SNAPSHOT + provided + + + javax.validation + validation-api + 2.0.0.Final + provided + + + diff --git a/AttributeStorage/src/main/java/com/comphenix/attribute/AttributeStorage.java b/src/main/java/com/comphenix/attribute/AttributeStorage.java similarity index 74% rename from AttributeStorage/src/main/java/com/comphenix/attribute/AttributeStorage.java rename to src/main/java/com/comphenix/attribute/AttributeStorage.java index f8e7b46..b0359bc 100644 --- a/AttributeStorage/src/main/java/com/comphenix/attribute/AttributeStorage.java +++ b/src/main/java/com/comphenix/attribute/AttributeStorage.java @@ -17,42 +17,45 @@ package com.comphenix.attribute; -import java.util.UUID; - -import org.bukkit.inventory.ItemStack; - import com.comphenix.attribute.Attributes.Attribute; import com.comphenix.attribute.Attributes.AttributeType; import com.comphenix.attribute.Attributes.Operation; import com.google.common.base.Objects; import com.google.common.base.Preconditions; +import org.bukkit.inventory.ItemStack; + +import java.util.UUID; /** * Store meta-data in an ItemStack as attributes. + * * @author Kristian */ +@SuppressWarnings({"unused", "WeakerAccess"}) public class AttributeStorage { - private ItemStack target; private final UUID uniqueKey; - + private ItemStack target; + private AttributeStorage(ItemStack target, UUID uniqueKey) { this.target = Preconditions.checkNotNull(target, "target cannot be NULL"); this.uniqueKey = Preconditions.checkNotNull(uniqueKey, "uniqueKey cannot be NULL"); } - + /** * Construct a new attribute storage system. *

* The key must be the same in order to retrieve the same data. - * @param target - the item stack where the data will be stored. + * + * @param target - the item stack where the data will be stored. * @param uniqueKey - the unique key used to retrieve the correct data. */ public static AttributeStorage newTarget(ItemStack target, UUID uniqueKey) { return new AttributeStorage(target, uniqueKey); } - + /** * Retrieve the data stored in the item's attribute. + * * @param defaultValue - the default value to return if no data can be found. * @return The stored data, or defaultValue if not found. */ @@ -60,17 +63,19 @@ public String getData(String defaultValue) { Attribute current = getAttribute(new Attributes(target), uniqueKey); return current != null ? current.getName() : defaultValue; } - + /** * Determine if we are storing any data. + * * @return TRUE if we are, FALSE otherwise. */ public boolean hasData() { - return getAttribute(new Attributes(target), uniqueKey) != null; + return getAttribute(new Attributes(target), uniqueKey) != null; } - + /** * Set the data stored in the attributes. + * * @param data - the data. */ public void setData(String data) { @@ -79,55 +84,69 @@ public void setData(String data) { if (current == null) { attributes.add( - Attribute.newBuilder(). - name(data). - amount(getBaseDamage(target)). - uuid(uniqueKey). - operation(Operation.ADD_NUMBER). - type(AttributeType.GENERIC_ATTACK_DAMAGE). - build() + Attribute.newBuilder(). + name(data). + amount(getBaseDamage(target)). + uuid(uniqueKey). + operation(Operation.ADD_NUMBER). + type(AttributeType.GENERIC_ATTACK_DAMAGE). + build() ); } else { current.setName(data); } this.target = attributes.getStack(); } - + /** * Retrieve the base damage of the given item. + * * @param stack - the stack. * @return The base damage. */ private int getBaseDamage(ItemStack stack) { - // Yes - we have to hard code these values. Cannot use Operation.ADD_PERCENTAGE either. - switch (stack.getType()) { - case WOOD_SWORD: return 4; - case GOLD_SWORD: return 4; - case STONE_SWORD: return 5; - case IRON_SWORD: return 6; - case DIAMOND_SWORD: return 7; - - case WOOD_AXE: return 3; - case GOLD_AXE: return 3; - case STONE_AXE: return 4; - case IRON_AXE: return 5; - case DIAMOND_AXE: return 6; - default: return 0; - } + // Yes - we have to hard code these values. Cannot use Operation.ADD_PERCENTAGE either. + switch (stack.getType()) { + case WOOD_SWORD: + return 4; + case GOLD_SWORD: + return 4; + case STONE_SWORD: + return 5; + case IRON_SWORD: + return 6; + case DIAMOND_SWORD: + return 7; + + case WOOD_AXE: + return 3; + case GOLD_AXE: + return 3; + case STONE_AXE: + return 4; + case IRON_AXE: + return 5; + case DIAMOND_AXE: + return 6; + default: + return 0; + } } - + /** * Retrieve the target stack. May have been changed. + * * @return The target stack. */ public ItemStack getTarget() { return target; } - + /** * Retrieve an attribute by UUID. + * * @param attributes - the attribute. - * @param id - the UUID to search for. + * @param id - the UUID to search for. * @return The first attribute associated with this UUID, or NULL. */ private Attribute getAttribute(Attributes attributes, UUID id) { diff --git a/AttributeStorage/src/main/java/com/comphenix/attribute/Attributes.java b/src/main/java/com/comphenix/attribute/Attributes.java similarity index 81% rename from AttributeStorage/src/main/java/com/comphenix/attribute/Attributes.java rename to src/main/java/com/comphenix/attribute/Attributes.java index db675bf..7e54893 100644 --- a/AttributeStorage/src/main/java/com/comphenix/attribute/Attributes.java +++ b/src/main/java/com/comphenix/attribute/Attributes.java @@ -17,39 +17,150 @@ package com.comphenix.attribute; -import java.util.Collections; -import java.util.Iterator; -import java.util.UUID; -import java.util.concurrent.ConcurrentMap; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bukkit.inventory.ItemStack; - import com.comphenix.attribute.NbtFactory.NbtCompound; import com.comphenix.attribute.NbtFactory.NbtList; -import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; +import org.bukkit.inventory.ItemStack; +import javax.validation.constraints.NotNull; +import java.util.Collections; +import java.util.Iterator; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +@SuppressWarnings({"unused", "WeakerAccess"}) public class Attributes { + // This may be modified + private ItemStack stack; + private NbtList attributes; + + public Attributes(ItemStack stack) { + // Create a CraftItemStack (under the hood) + this.stack = NbtFactory.getCraftItemStack(stack); + loadAttributes(false); + } + + /** + * Load the NBT list from the TAG compound. + * + * @param createIfMissing - create the list if its missing. + */ + private void loadAttributes(boolean createIfMissing) { + if (this.attributes == null) { + NbtCompound nbt = NbtFactory.fromItemTag(this.stack); + this.attributes = nbt.getList("AttributeModifiers", createIfMissing); + } + } + + /** + * Remove the NBT list from the TAG compound. + */ + private void removeAttributes() { + NbtCompound nbt = NbtFactory.fromItemTag(this.stack); + nbt.remove("AttributeModifiers"); + this.attributes = null; + } + + /** + * Retrieve the modified item stack. + * + * @return The modified item stack. + */ + public ItemStack getStack() { + return stack; + } + + /** + * Retrieve the number of attributes. + * + * @return Number of attributes. + */ + public int size() { + return attributes != null ? attributes.size() : 0; + } + + /** + * Add a new attribute to the list. + * + * @param attribute - the new attribute. + */ + public void add(Attribute attribute) { + Preconditions.checkNotNull(attribute.getName(), "must specify an attribute name."); + loadAttributes(true); + attributes.add(attribute.data); + } + + /** + * Remove the first instance of the given attribute. + *

+ * The attribute will be removed using its UUID. + * + * @param attribute - the attribute to remove. + * @return TRUE if the attribute was removed, FALSE otherwise. + */ + public boolean remove(Attribute attribute) { + if (attributes == null) + return false; + UUID uuid = attribute.getUUID(); + + for (Iterator it = values().iterator(); it.hasNext(); ) { + if (Objects.equal(it.next().getUUID(), uuid)) { + it.remove(); + + // Last removed attribute? + if (size() == 0) { + removeAttributes(); + } + return true; + } + } + return false; + } + + /** + * Remove every attribute. + */ + public void clear() { + removeAttributes(); + } + + /** + * Retrieve the attribute at a given index. + * + * @param index - the index to look up. + * @return The attribute at that index. + */ + public Attribute get(int index) { + if (size() == 0) + throw new IllegalStateException("Attribute list is empty."); + return new Attribute((NbtCompound) attributes.get(index)); + } + + // We can't make Attributes itself iterable without splitting it up into separate classes + public Iterable values() { + return () -> { + // Handle the empty case + if (size() == 0) + return Collections.emptyList().iterator(); + + return Iterators.transform(attributes.iterator(), + element -> new Attribute((NbtCompound) element)); + }; + } + public enum Operation { ADD_NUMBER(0), MULTIPLY_PERCENTAGE(1), ADD_PERCENTAGE(2); private int id; - - private Operation(int id) { + + Operation(int id) { this.id = id; } - - public int getId() { - return id; - } - + public static Operation fromId(int id) { // Linear scan is very fast for small N for (Operation op : values()) { @@ -59,8 +170,12 @@ public static Operation fromId(int id) { } throw new IllegalArgumentException("Corrupt operation ID " + id + " detected."); } + + public int getId() { + return id; + } } - + public static class AttributeType { private static ConcurrentMap LOOKUP = Maps.newConcurrentMap(); public static final AttributeType GENERIC_MAX_HEALTH = new AttributeType("generic.maxHealth").register(); @@ -68,53 +183,60 @@ public static class AttributeType { public static final AttributeType GENERIC_ATTACK_DAMAGE = new AttributeType("generic.attackDamage").register(); public static final AttributeType GENERIC_MOVEMENT_SPEED = new AttributeType("generic.movementSpeed").register(); public static final AttributeType GENERIC_KNOCKBACK_RESISTANCE = new AttributeType("generic.knockbackResistance").register(); - + public static final AttributeType GENERIC_ARMOR = new AttributeType("generic.armor").register(); + public static final AttributeType GENERIC_ARMOR_TOUGHNESS = new AttributeType("generic.armorToughness").register(); + private final String minecraftId; - + /** * Construct a new attribute type. *

* Remember to {@link #register()} the type. + * * @param minecraftId - the ID of the type. */ public AttributeType(String minecraftId) { this.minecraftId = minecraftId; } - - /** - * Retrieve the associated minecraft ID. - * @return The associated ID. - */ - public String getMinecraftId() { - return minecraftId; - } - - /** - * Register the type in the central registry. - * @return The registered type. - */ - // Constructors should have no side-effects! - public AttributeType register() { - AttributeType old = LOOKUP.putIfAbsent(minecraftId, this); - return old != null ? old : this; - } - + /** * Retrieve the attribute type associated with a given ID. + * * @param minecraftId The ID to search for. * @return The attribute type, or NULL if not found. */ public static AttributeType fromId(String minecraftId) { return LOOKUP.get(minecraftId); } - + /** * Retrieve every registered attribute type. + * * @return Every type. */ public static Iterable values() { return LOOKUP.values(); } + + /** + * Retrieve the associated minecraft ID. + * + * @return The associated ID. + */ + public String getMinecraftId() { + return minecraftId; + } + + /** + * Register the type in the central registry. + * + * @return The registered type. + */ + // Constructors should have no side-effects! + public AttributeType register() { + AttributeType old = LOOKUP.putIfAbsent(minecraftId, this); + return old != null ? old : this; + } } public static class Attribute { @@ -127,12 +249,22 @@ private Attribute(Builder builder) { setAttributeType(builder.type); setName(builder.name); setUUID(builder.uuid); + setSlot(builder.slot); } - + private Attribute(NbtCompound data) { this.data = data; } - + + /** + * Construct a new attribute builder with a random UUID and default operation of adding numbers. + * + * @return The attribute builder. + */ + public static Builder newBuilder() { + return new Builder().uuid(UUID.randomUUID()).operation(Operation.ADD_NUMBER); + } + public double getAmount() { return data.getDouble("Amount", 0.0); } @@ -145,7 +277,7 @@ public Operation getOperation() { return Operation.fromId(data.getInteger("Operation", 0)); } - public void setOperation(@Nonnull Operation operation) { + public void setOperation(@NotNull Operation operation) { Preconditions.checkNotNull(operation, "operation cannot be NULL."); data.put("Operation", operation.getId()); } @@ -154,7 +286,7 @@ public AttributeType getAttributeType() { return AttributeType.fromId(data.getString("AttributeName", null)); } - public void setAttributeType(@Nonnull AttributeType type) { + public void setAttributeType(@NotNull AttributeType type) { Preconditions.checkNotNull(type, "type cannot be NULL."); data.put("AttributeName", type.getMinecraftId()); } @@ -163,7 +295,7 @@ public String getName() { return data.getString("Name", null); } - public void setName(@Nonnull String name) { + public void setName(@NotNull String name) { Preconditions.checkNotNull(name, "name cannot be NULL."); data.put("Name", name); } @@ -172,20 +304,29 @@ public UUID getUUID() { return new UUID(data.getLong("UUIDMost", null), data.getLong("UUIDLeast", null)); } - public void setUUID(@Nonnull UUID id) { + public void setUUID(@NotNull UUID id) { Preconditions.checkNotNull("id", "id cannot be NULL."); data.put("UUIDLeast", id.getLeastSignificantBits()); data.put("UUIDMost", id.getMostSignificantBits()); } - /** - * Construct a new attribute builder with a random UUID and default operation of adding numbers. - * @return The attribute builder. - */ - public static Builder newBuilder() { - return new Builder().uuid(UUID.randomUUID()).operation(Operation.ADD_NUMBER); + public String getSlot() { + return data.getString("Slot", null); + } + + public void setSlot(String slot) { + Preconditions.checkNotNull(slot, "slot cannot be NULL."); + data.put("Slot", slot); } - + + public NbtCompound getData() { + return data; + } + + public void setData(NbtCompound data) { + this.data = data; + } + // Makes it easier to construct an attribute public static class Builder { private double amount; @@ -193,154 +334,45 @@ public static class Builder { private AttributeType type; private String name; private UUID uuid; + private String slot; private Builder() { // Don't make this accessible } - + public Builder amount(double amount) { this.amount = amount; return this; } + public Builder operation(Operation operation) { this.operation = operation; return this; } + public Builder type(AttributeType type) { this.type = type; return this; } + public Builder name(String name) { this.name = name; return this; } + public Builder uuid(UUID uuid) { this.uuid = uuid; return this; } + + public Builder slot(String slot) { + this.slot = slot; + return this; + } + public Attribute build() { return new Attribute(this); } } } - - // This may be modified - public ItemStack stack; - private NbtList attributes; - - public Attributes(ItemStack stack) { - // Create a CraftItemStack (under the hood) - this.stack = NbtFactory.getCraftItemStack(stack); - loadAttributes(false); - } - - /** - * Load the NBT list from the TAG compound. - * @param createIfMissing - create the list if its missing. - */ - private void loadAttributes(boolean createIfMissing) { - if (this.attributes == null) { - NbtCompound nbt = NbtFactory.fromItemTag(this.stack); - this.attributes = nbt.getList("AttributeModifiers", createIfMissing); - } - } - - /** - * Remove the NBT list from the TAG compound. - */ - private void removeAttributes() { - NbtCompound nbt = NbtFactory.fromItemTag(this.stack); - nbt.remove("AttributeModifiers"); - this.attributes = null; - } - - /** - * Retrieve the modified item stack. - * @return The modified item stack. - */ - public ItemStack getStack() { - return stack; - } - - /** - * Retrieve the number of attributes. - * @return Number of attributes. - */ - public int size() { - return attributes != null ? attributes.size() : 0; - } - - /** - * Add a new attribute to the list. - * @param attribute - the new attribute. - */ - public void add(Attribute attribute) { - Preconditions.checkNotNull(attribute.getName(), "must specify an attribute name."); - loadAttributes(true); - attributes.add(attribute.data); - } - - /** - * Remove the first instance of the given attribute. - *

- * The attribute will be removed using its UUID. - * @param attribute - the attribute to remove. - * @return TRUE if the attribute was removed, FALSE otherwise. - */ - public boolean remove(Attribute attribute) { - if (attributes == null) - return false; - UUID uuid = attribute.getUUID(); - - for (Iterator it = values().iterator(); it.hasNext(); ) { - if (Objects.equal(it.next().getUUID(), uuid)) { - it.remove(); - - // Last removed attribute? - if (size() == 0) { - removeAttributes(); - } - return true; - } - } - return false; - } - - /** - * Remove every attribute. - */ - public void clear() { - removeAttributes(); - } - - /** - * Retrieve the attribute at a given index. - * @param index - the index to look up. - * @return The attribute at that index. - */ - public Attribute get(int index) { - if (size() == 0) - throw new IllegalStateException("Attribute list is empty."); - return new Attribute((NbtCompound) attributes.get(index)); - } - - // We can't make Attributes itself iterable without splitting it up into separate classes - public Iterable values() { - return new Iterable() { - @Override - public Iterator iterator() { - // Handle the empty case - if (size() == 0) - return Collections.emptyList().iterator(); - - return Iterators.transform(attributes.iterator(), - new Function() { - @Override - public Attribute apply(@Nullable Object element) { - return new Attribute((NbtCompound) element); - } - }); - } - }; - } } diff --git a/AttributeStorage/src/main/java/com/comphenix/attribute/NbtFactory.java b/src/main/java/com/comphenix/attribute/NbtFactory.java similarity index 75% rename from AttributeStorage/src/main/java/com/comphenix/attribute/NbtFactory.java rename to src/main/java/com/comphenix/attribute/NbtFactory.java index cedf6bc..ac56993 100644 --- a/AttributeStorage/src/main/java/com/comphenix/attribute/NbtFactory.java +++ b/src/main/java/com/comphenix/attribute/NbtFactory.java @@ -17,93 +17,38 @@ package com.comphenix.attribute; -import java.io.BufferedInputStream; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.AbstractList; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.inventory.ItemStack; - import com.google.common.base.Splitter; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; +import com.google.common.io.ByteSink; +import com.google.common.io.ByteSource; import com.google.common.io.Closeables; -import com.google.common.io.Files; -import com.google.common.io.InputSupplier; -import com.google.common.io.OutputSupplier; import com.google.common.primitives.Primitives; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.ItemStack; -public class NbtFactory { +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class NbtFactory { // Convert between NBT id and the equivalent class in java private static final BiMap> NBT_CLASS = HashBiMap.create(); private static final BiMap NBT_ENUM = HashBiMap.create(); - - /** - * Whether or not to enable stream compression. - * @author Kristian - */ - public enum StreamOptions { - NO_COMPRESSION, - GZIP_COMPRESSION, - } - - private enum NbtType { - TAG_END(0, Void.class), - TAG_BYTE(1, byte.class), - TAG_SHORT(2, short.class), - TAG_INT(3, int.class), - TAG_LONG(4, long.class), - TAG_FLOAT(5, float.class), - TAG_DOUBLE(6, double.class), - TAG_BYTE_ARRAY(7, byte[].class), - TAG_INT_ARRAY(11, int[].class), - TAG_STRING(8, String.class), - TAG_LIST(9, List.class), - TAG_COMPOUND(10, Map.class); - - // Unique NBT id - public final int id; - - private NbtType(int id, Class type) { - this.id = id; - NBT_CLASS.put(id, type); - NBT_ENUM.put(id, this); - } - - private String getFieldName() { - if (this == TAG_COMPOUND) - return "map"; - else if (this == TAG_LIST) - return "list"; - else - return "data"; - } - } - + // Shared instance + private static NbtFactory INSTANCE; + private final Field[] DATA_FIELD = new Field[12]; // The NBT base class private Class BASE_CLASS; private Class COMPOUND_CLASS; @@ -112,395 +57,174 @@ else if (this == TAG_LIST) private Method NBT_CREATE_TAG; private Method NBT_GET_TYPE; private Field NBT_LIST_TYPE; - private final Field[] DATA_FIELD = new Field[12]; - // CraftItemStack private Class CRAFT_STACK; private Field CRAFT_HANDLE; private Field STACK_TAG; - // Loading/saving compounds private LoadCompoundMethod LOAD_COMPOUND; private Method SAVE_COMPOUND; - - // Shared instance - private static NbtFactory INSTANCE; - - /** - * Represents a root NBT compound. - *

- * All changes to this map will be reflected in the underlying NBT compound. Values may only be one of the following: - *

    - *
  • Primitive types
  • - *
  • {@link java.lang.String String}
  • - *
  • {@link NbtList}
  • - *
  • {@link NbtCompound}
  • - *
- *

- * See also: - *

    - *
  • {@link NbtFactory#createCompound()}
  • - *
  • {@link NbtFactory#fromCompound(Object)}
  • - *
- * @author Kristian - */ - public final class NbtCompound extends ConvertedMap { - private NbtCompound(Object handle) { - super(handle, getDataMap(handle)); - } - // Simplifiying access to each value - public Byte getByte(String key, Byte defaultValue) { - return containsKey(key) ? (Byte)get(key) : defaultValue; - } - public Short getShort(String key, Short defaultValue) { - return containsKey(key) ? (Short)get(key) : defaultValue; - } - public Integer getInteger(String key, Integer defaultValue) { - return containsKey(key) ? (Integer)get(key) : defaultValue; - } - public Long getLong(String key, Long defaultValue) { - return containsKey(key) ? (Long)get(key) : defaultValue; - } - public Float getFloat(String key, Float defaultValue) { - return containsKey(key) ? (Float)get(key) : defaultValue; - } - public Double getDouble(String key, Double defaultValue) { - return containsKey(key) ? (Double)get(key) : defaultValue; - } - public String getString(String key, String defaultValue) { - return containsKey(key) ? (String)get(key) : defaultValue; - } - public byte[] getByteArray(String key, byte[] defaultValue) { - return containsKey(key) ? (byte[])get(key) : defaultValue; - } - public int[] getIntegerArray(String key, int[] defaultValue) { - return containsKey(key) ? (int[])get(key) : defaultValue; - } - - /** - * Retrieve the list by the given name. - * @param key - the name of the list. - * @param createNew - whether or not to create a new list if its missing. - * @return An existing list, a new list or NULL. - */ - public NbtList getList(String key, boolean createNew) { - NbtList list = (NbtList) get(key); - - if (list == null && createNew) - put(key, list = createList()); - return list; - } - - /** - * Retrieve the map by the given name. - * @param key - the name of the map. - * @param createNew - whether or not to create a new map if its missing. - * @return An existing map, a new map or NULL. - */ - public NbtCompound getMap(String key, boolean createNew) { - return getMap(Arrays.asList(key), createNew); - } - // Done - - /** - * Set the value of an entry at a given location. - *

- * Every element of the path (except the end) are assumed to be compounds, and will - * be created if they are missing. - * @param path - the path to the entry. - * @param value - the new value of this entry. - * @return This compound, for chaining. - */ - public NbtCompound putPath(String path, Object value) { - List entries = getPathElements(path); - Map map = getMap(entries.subList(0, entries.size() - 1), true); - - map.put(entries.get(entries.size() - 1), value); - return this; - } - - /** - * Retrieve the value of a given entry in the tree. - *

- * Every element of the path (except the end) are assumed to be compounds. The - * retrieval operation will be cancelled if any of them are missing. - * @param path - path to the entry. - * @return The value, or NULL if not found. - */ - @SuppressWarnings("unchecked") - public T getPath(String path) { - List entries = getPathElements(path); - NbtCompound map = getMap(entries.subList(0, entries.size() - 1), false); - - if (map != null) { - return (T) map.get(entries.get(entries.size() - 1)); - } - return null; - } - - /** - * Save the content of a NBT compound to a stream. - *

- * Use {@link Files#newOutputStreamSupplier(java.io.File)} to provide a stream supplier to a file. - * @param stream - the output stream. - * @param option - whether or not to compress the output. - * @throws IOException If anything went wrong. - */ - public void saveTo(OutputSupplier stream, StreamOptions option) throws IOException { - saveStream(this, stream, option); - } - - /** - * Retrieve a map from a given path. - * @param path - path of compounds to look up. - * @param createNew - whether or not to create new compounds on the way. - * @return The map at this location. - */ - private NbtCompound getMap(Iterable path, boolean createNew) { - NbtCompound current = this; - - for (String entry : path) { - NbtCompound child = (NbtCompound) current.get(entry); - - if (child == null) { - if (!createNew) - return null; - current.put(entry, child = createCompound()); - } - current = child; - } - return current; - } - - /** - * Split the path into separate elements. - * @param path - the path to split. - * @return The elements. - */ - private List getPathElements(String path) { - return Lists.newArrayList(Splitter.on(".").omitEmptyStrings().split(path)); - } - } - - /** - * Represents a root NBT list. - * See also: - *

    - *
  • {@link NbtFactory#createNbtList()}
  • - *
  • {@link NbtFactory#fromList(Object)}
  • - *
- * @author Kristian - */ - public final class NbtList extends ConvertedList { - private NbtList(Object handle) { - super(handle, getDataList(handle)); - } - } - - /** - * Represents an object that provides a view of a native NMS class. - * @author Kristian - */ - public static interface Wrapper { - /** - * Retrieve the underlying native NBT tag. - * @return The underlying NBT. - */ - public Object getHandle(); - } - - /** - * Retrieve or construct a shared NBT factory. - * @return The factory. - */ - private static NbtFactory get() { - if (INSTANCE == null) - INSTANCE = new NbtFactory(); - return INSTANCE; - } - /** * Construct an instance of the NBT factory by deducing the class of NBTBase. */ private NbtFactory() { if (BASE_CLASS == null) { try { - // Keep in mind that I do use hard-coded field names - but it's okay as long as we're dealing + // Keep in mind that I do use hard-coded field names - but it's okay as long as we're dealing // with CraftBukkit or its derivatives. This does not work in MCPC+ however. ClassLoader loader = NbtFactory.class.getClassLoader(); - + String packageName = getPackageName(); Class offlinePlayer = loader.loadClass(packageName + ".CraftOfflinePlayer"); - + // Prepare NBT COMPOUND_CLASS = getMethod(0, Modifier.STATIC, offlinePlayer, "getData").getReturnType(); BASE_CLASS = COMPOUND_CLASS.getSuperclass(); NBT_GET_TYPE = getMethod(0, Modifier.STATIC, BASE_CLASS, "getTypeId"); NBT_CREATE_TAG = getMethod(Modifier.STATIC, 0, BASE_CLASS, "createTag", byte.class); - + // Prepare CraftItemStack CRAFT_STACK = loader.loadClass(packageName + ".inventory.CraftItemStack"); CRAFT_HANDLE = getField(null, CRAFT_STACK, "handle"); STACK_TAG = getField(null, CRAFT_HANDLE.getType(), "tag"); - + // Loading/saving String nmsPackage = BASE_CLASS.getPackage().getName(); - initializeNMS(loader, nmsPackage); - - LOAD_COMPOUND = READ_LIMITER_CLASS != null ? - new LoadMethodSkinUpdate(STREAM_TOOLS, READ_LIMITER_CLASS) : - new LoadMethodWorldUpdate(STREAM_TOOLS); + initializeNMS(loader, nmsPackage); + + LOAD_COMPOUND = READ_LIMITER_CLASS != null ? + new LoadMethodSkinUpdate(STREAM_TOOLS, READ_LIMITER_CLASS) : + new LoadMethodWorldUpdate(STREAM_TOOLS); SAVE_COMPOUND = getMethod(Modifier.STATIC, 0, STREAM_TOOLS, null, BASE_CLASS, DataOutput.class); - + } catch (ClassNotFoundException e) { throw new IllegalStateException("Unable to find offline player.", e); } } } - private void initializeNMS(ClassLoader loader, String nmsPackage) { - try { - STREAM_TOOLS = loader.loadClass(nmsPackage + ".NBTCompressedStreamTools"); - READ_LIMITER_CLASS = loader.loadClass(nmsPackage + ".NBTReadLimiter"); - } catch (ClassNotFoundException e) { - // Ignore - we will detect this later - } - } - - private String getPackageName() { - Server server = Bukkit.getServer(); - String name = server != null ? server.getClass().getPackage().getName() : null; - - if (name != null && name.contains("craftbukkit")) { - return name; - } else { - // Fallback - return "org.bukkit.craftbukkit.v1_7_R3"; - } - } - - @SuppressWarnings("unchecked") - private Map getDataMap(Object handle) { - return (Map) getFieldValue( - getDataField(NbtType.TAG_COMPOUND, handle), handle); - } - - @SuppressWarnings("unchecked") - private List getDataList(Object handle) { - return (List) getFieldValue( - getDataField(NbtType.TAG_LIST, handle), handle); + /** + * Retrieve or construct a shared NBT factory. + * + * @return The factory. + */ + private static NbtFactory get() { + if (INSTANCE == null) + INSTANCE = new NbtFactory(); + return INSTANCE; } - + /** * Construct a new NBT list of an unspecified type. + * * @return The NBT list. */ public static NbtList createList(Object... content) { return createList(Arrays.asList(content)); } - + /** * Construct a new NBT list of an unspecified type. + * * @return The NBT list. */ - public static NbtList createList(Iterable iterable) { + public static NbtList createList(Iterable iterable) { NbtList list = get().new NbtList( - INSTANCE.createNbtTag(NbtType.TAG_LIST, null) + INSTANCE.createNbtTag(NbtType.TAG_LIST, null) ); - + // Add the content as well - for (Object obj : iterable) + for (Object obj : iterable) list.add(obj); return list; } - + /** * Construct a new NBT compound. - *

- * Use {@link NbtCompound#asMap()} to modify it. + * * @return The NBT compound. */ public static NbtCompound createCompound() { return get().new NbtCompound( - INSTANCE.createNbtTag(NbtType.TAG_COMPOUND, null) + INSTANCE.createNbtTag(NbtType.TAG_COMPOUND, null) ); } - + /** * Construct a new NBT wrapper from a list. + * * @param nmsList - the NBT list. * @return The wrapper. */ public static NbtList fromList(Object nmsList) { return get().new NbtList(nmsList); } - + /** * Load the content of a file from a stream. - *

- * Use {@link Files#newInputStreamSupplier(java.io.File)} to provide a stream from a file. + * * @param stream - the stream supplier. * @param option - whether or not to decompress the input stream. * @return The decoded NBT compound. * @throws IOException If anything went wrong. */ - public static NbtCompound fromStream(InputSupplier stream, StreamOptions option) throws IOException { + public static NbtCompound fromStream(ByteSource stream, StreamOptions option) throws IOException { InputStream input = null; DataInputStream data = null; boolean suppress = true; - + try { - input = stream.getInput(); + input = stream.openStream(); data = new DataInputStream(new BufferedInputStream( - option == StreamOptions.GZIP_COMPRESSION ? new GZIPInputStream(input) : input + option == StreamOptions.GZIP_COMPRESSION ? new GZIPInputStream(input) : input )); - + NbtCompound result = fromCompound(get().LOAD_COMPOUND.loadNbt(data)); suppress = false; return result; - + } finally { if (data != null) Closeables.close(data, suppress); else if (input != null) - Closeables.close(input, suppress); + Closeables.close(input, true); } } - + /** * Save the content of a NBT compound to a stream. - *

- * Use {@link Files#newOutputStreamSupplier(java.io.File)} to provide a stream supplier to a file. + * * @param source - the NBT compound to save. * @param stream - the stream. * @param option - whether or not to compress the output. * @throws IOException If anything went wrong. */ - public static void saveStream(NbtCompound source, OutputSupplier stream, StreamOptions option) throws IOException { + public static void saveStream(NbtCompound source, ByteSink stream, StreamOptions option) throws IOException { OutputStream output = null; DataOutputStream data = null; boolean suppress = true; - + try { - output = stream.getOutput(); + output = stream.openStream(); data = new DataOutputStream( - option == StreamOptions.GZIP_COMPRESSION ? new GZIPOutputStream(output) : output + option == StreamOptions.GZIP_COMPRESSION ? new GZIPOutputStream(output) : output ); - + invokeMethod(get().SAVE_COMPOUND, null, source.getHandle(), data); suppress = false; - + } finally { if (data != null) Closeables.close(data, suppress); else if (output != null) - Closeables.close(output, suppress); + Closeables.close(output, true); } } - + /** * Construct a new NBT wrapper from a compound. + * * @param nmsCompound - the NBT compund. * @return The wrapper. */ @@ -510,17 +234,15 @@ public static NbtCompound fromCompound(Object nmsCompound) { /** * Set the NBT compound tag of a given item stack. - *

- * The item stack must be a wrapper for a CraftItemStack. Use - * {@link MinecraftReflection#getBukkitItemStack(ItemStack)} if not. - * @param stack - the item stack, cannot be air. + * + * @param stack - the item stack, cannot be air. * @param compound - the new NBT compound, or NULL to remove it. * @throws IllegalArgumentException If the stack is not a CraftItemStack, or it represents air. */ public static void setItemTag(ItemStack stack, NbtCompound compound) { checkItemStack(stack); Object nms = getFieldValue(get().CRAFT_HANDLE, stack); - + // Now update the tag compound setFieldValue(get().STACK_TAG, nms, compound.getHandle()); } @@ -531,6 +253,7 @@ public static void setItemTag(ItemStack stack, NbtCompound compound) { * material, damage value or count. *

* The item stack must be a wrapper for a CraftItemStack. + * * @param stack - the item stack. * @return A wrapper for its NBT tag. */ @@ -538,7 +261,7 @@ public static NbtCompound fromItemTag(ItemStack stack) { checkItemStack(stack); Object nms = getFieldValue(get().CRAFT_HANDLE, stack); Object tag = getFieldValue(get().STACK_TAG, nms); - + // Create the tag if it doesn't exist if (tag == null) { NbtCompound compound = createCompound(); @@ -547,9 +270,10 @@ public static NbtCompound fromItemTag(ItemStack stack) { } return fromCompound(tag); } - + /** * Retrieve a CraftItemStack version of the stack. + * * @param stack - the stack to convert. * @return The CraftItemStack version. */ @@ -566,9 +290,10 @@ public static ItemStack getCraftItemStack(ItemStack stack) { throw new IllegalStateException("Unable to convert " + stack + " + to a CraftItemStack."); } } - + /** * Ensure that the given stack can store arbitrary NBT information. + * * @param stack - the stack to check. */ private static void checkItemStack(ItemStack stack) { @@ -579,34 +304,155 @@ private static void checkItemStack(ItemStack stack) { if (stack.getType() == Material.AIR) throw new IllegalArgumentException("ItemStacks representing air cannot store NMS information."); } - + + /** + * Invoke a method on the given target instance using the provided parameters. + * + * @param method - the method to invoke. + * @param target - the target. + * @param params - the parameters to supply. + * @return The result of the method. + */ + private static Object invokeMethod(Method method, Object target, Object... params) { + try { + return method.invoke(target, params); + } catch (Exception e) { + throw new RuntimeException("Unable to invoke method " + method + " for " + target, e); + } + } + + private static void setFieldValue(Field field, Object target, Object value) { + try { + field.set(target, value); + } catch (Exception e) { + throw new RuntimeException("Unable to set " + field + " for " + target, e); + } + } + + private static Object getFieldValue(Field field, Object target) { + try { + return field.get(target); + } catch (Exception e) { + throw new RuntimeException("Unable to retrieve " + field + " for " + target, e); + } + } + + /** + * Search for the first publically and privately defined method of the given name and parameter count. + * + * @param requireMod - modifiers that are required. + * @param bannedMod - modifiers that are banned. + * @param clazz - a class to start with. + * @param methodName - the method name, or NULL to skip. + * @param params - the expected parameters. + * @return The first method by this name. + * @throws IllegalStateException If we cannot find this method. + */ + private static Method getMethod(int requireMod, int bannedMod, Class clazz, String methodName, Class... params) { + for (Method method : clazz.getDeclaredMethods()) { + // Limitation: Doesn't handle overloads + if ((method.getModifiers() & requireMod) == requireMod && + (method.getModifiers() & bannedMod) == 0 && + (methodName == null || method.getName().equals(methodName)) && + Arrays.equals(method.getParameterTypes(), params)) { + + method.setAccessible(true); + return method; + } + } + // Search in every superclass + if (clazz.getSuperclass() != null) + return getMethod(requireMod, bannedMod, clazz.getSuperclass(), methodName, params); + throw new IllegalStateException(String.format( + "Unable to find method %s (%s).", methodName, Arrays.asList(params))); + } + + /** + * Search for the first publically and privately defined field of the given name. + * + * @param instance - an instance of the class with the field. + * @param clazz - an optional class to start with, or NULL to deduce it from instance. + * @param fieldName - the field name. + * @return The first field by this name. + * @throws IllegalStateException If we cannot find this field. + */ + private static Field getField(Object instance, Class clazz, String fieldName) { + if (clazz == null) + clazz = instance.getClass(); + // Ignore access rules + for (Field field : clazz.getDeclaredFields()) { + if (field.getName().equals(fieldName)) { + field.setAccessible(true); + return field; + } + } + // Recursively fild the correct field + if (clazz.getSuperclass() != null) + return getField(instance, clazz.getSuperclass(), fieldName); + throw new IllegalStateException("Unable to find field " + fieldName + " in " + instance); + } + + private void initializeNMS(ClassLoader loader, String nmsPackage) { + try { + STREAM_TOOLS = loader.loadClass(nmsPackage + ".NBTCompressedStreamTools"); + READ_LIMITER_CLASS = loader.loadClass(nmsPackage + ".NBTReadLimiter"); + } catch (ClassNotFoundException e) { + // Ignore - we will detect this later + } + } + + private String getPackageName() { + Server server = Bukkit.getServer(); + String name = server != null ? server.getClass().getPackage().getName() : null; + + if (name != null && name.contains("craftbukkit")) { + return name; + } else { + // Fallback + return "org.bukkit.craftbukkit.v1_12_R1"; + } + } + + @SuppressWarnings("unchecked") + private Map getDataMap(Object handle) { + return (Map) getFieldValue( + getDataField(NbtType.TAG_COMPOUND, handle), handle); + } + + @SuppressWarnings("unchecked") + private List getDataList(Object handle) { + return (List) getFieldValue( + getDataField(NbtType.TAG_LIST, handle), handle); + } + /** * Convert wrapped List and Map objects into their respective NBT counterparts. - * @param name - the name of the NBT element to create. + * * @param value - the value of the element to create. Can be a List or a Map. * @return The NBT element. */ private Object unwrapValue(Object value) { if (value == null) return null; - + if (value instanceof Wrapper) { return ((Wrapper) value).getHandle(); - + } else if (value instanceof List) { throw new IllegalArgumentException("Can only insert a WrappedList."); } else if (value instanceof Map) { throw new IllegalArgumentException("Can only insert a WrappedCompound."); - + } else { return createNbtTag(getPrimitiveType(value), value); } } - + /** * Convert a given NBT element to a primitive wrapper or List/Map equivalent. *

* All changes to any mutable objects will be reflected in the underlying NBT element(s). + * * @param nms - the NBT element. * @return The wrapper equivalent. */ @@ -616,7 +462,7 @@ private Object wrapNative(Object nms) { if (BASE_CLASS.isAssignableFrom(nms.getClass())) { final NbtType type = getNbtType(nms); - + // Handle the different types switch (type) { case TAG_COMPOUND: @@ -629,36 +475,39 @@ private Object wrapNative(Object nms) { } throw new IllegalArgumentException("Unexpected type: " + nms); } - + /** * Construct a new NMS NBT tag initialized with the given value. - * @param type - the NBT type. + * + * @param type - the NBT type. * @param value - the value, or NULL to keep the original value. * @return The created tag. */ private Object createNbtTag(NbtType type, Object value) { - Object tag = invokeMethod(NBT_CREATE_TAG, null, (byte)type.id); + Object tag = invokeMethod(NBT_CREATE_TAG, null, (byte) type.id); if (value != null) { setFieldValue(getDataField(type, tag), tag, value); } return tag; } - + /** * Retrieve the field where the NBT class stores its value. + * * @param type - the NBT type. - * @param nms - the NBT class instance. + * @param nms - the NBT class instance. * @return The corresponding field. */ private Field getDataField(NbtType type, Object nms) { - if (DATA_FIELD[type.id] == null) + if (DATA_FIELD[type.id] == null) DATA_FIELD[type.id] = getField(nms, null, type.getFieldName()); return DATA_FIELD[type.id]; } - + /** * Retrieve the NBT type from a given NMS NBT tag. + * * @param nms - the native NBT tag. * @return The corresponding type. */ @@ -666,245 +515,474 @@ private NbtType getNbtType(Object nms) { int type = (Byte) invokeMethod(NBT_GET_TYPE, nms); return NBT_ENUM.get(type); } - + /** * Retrieve the nearest NBT type for a given primitive type. + * * @param primitive - the primitive type. * @return The corresponding type. */ private NbtType getPrimitiveType(Object primitive) { NbtType type = NBT_ENUM.get(NBT_CLASS.inverse().get( - Primitives.unwrap(primitive.getClass()) + Primitives.unwrap(primitive.getClass()) )); - + // Display the illegal value at least if (type == null) throw new IllegalArgumentException(String.format( - "Illegal type: %s (%s)", primitive.getClass(), primitive)); + "Illegal type: %s (%s)", primitive.getClass(), primitive)); return type; } /** - * Invoke a method on the given target instance using the provided parameters. - * @param method - the method to invoke. - * @param target - the target. - * @param params - the parameters to supply. - * @return The result of the method. + * Whether or not to enable stream compression. + * + * @author Kristian + */ + public enum StreamOptions { + NO_COMPRESSION, + GZIP_COMPRESSION, + } + + private enum NbtType { + TAG_END(0, Void.class), + TAG_BYTE(1, byte.class), + TAG_SHORT(2, short.class), + TAG_INT(3, int.class), + TAG_LONG(4, long.class), + TAG_FLOAT(5, float.class), + TAG_DOUBLE(6, double.class), + TAG_BYTE_ARRAY(7, byte[].class), + TAG_INT_ARRAY(11, int[].class), + TAG_STRING(8, String.class), + TAG_LIST(9, List.class), + TAG_COMPOUND(10, Map.class); + + // Unique NBT id + public final int id; + + NbtType(int id, Class type) { + this.id = id; + NBT_CLASS.put(id, type); + NBT_ENUM.put(id, this); + } + + private String getFieldName() { + if (this == TAG_COMPOUND) + return "map"; + else if (this == TAG_LIST) + return "list"; + else + return "data"; + } + } + + /** + * Represents an object that provides a view of a native NMS class. + * + * @author Kristian + */ + public interface Wrapper { + /** + * Retrieve the underlying native NBT tag. + * + * @return The underlying NBT. + */ + Object getHandle(); + } + + /** + * Represents a method for loading an NBT compound. + * + * @author Kristian */ - private static Object invokeMethod(Method method, Object target, Object... params) { - try { - return method.invoke(target, params); - } catch (Exception e) { - throw new RuntimeException("Unable to invoke method " + method + " for " + target, e); + private static abstract class LoadCompoundMethod { + protected Method staticMethod; + + protected void setMethod(Method method) { + this.staticMethod = method; + this.staticMethod.setAccessible(true); } + + /** + * Load an NBT compound from a given stream. + * + * @param input - the input stream. + * @return The loaded NBT compound. + */ + public abstract Object loadNbt(DataInput input); } - - private static void setFieldValue(Field field, Object target, Object value) { - try { - field.set(target, value); - } catch (Exception e) { - throw new RuntimeException("Unable to set " + field + " for " + target, e); + + /** + * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.2 - 1.7.5 + */ + private static class LoadMethodWorldUpdate extends LoadCompoundMethod { + public LoadMethodWorldUpdate(Class streamClass) { + setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class)); } - } - - private static Object getFieldValue(Field field, Object target) { - try { - return field.get(target); - } catch (Exception e) { - throw new RuntimeException("Unable to retrieve " + field + " for " + target, e); + + @Override + public Object loadNbt(DataInput input) { + return invokeMethod(staticMethod, null, input); } } - + /** - * Search for the first publically and privately defined method of the given name and parameter count. - * @param requireMod - modifiers that are required. - * @param bannedMod - modifiers that are banned. - * @param clazz - a class to start with. - * @param methodName - the method name, or NULL to skip. - * @param params - the expected parameters. - * @return The first method by this name. - * @throws IllegalStateException If we cannot find this method. + * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.8 */ - private static Method getMethod(int requireMod, int bannedMod, Class clazz, String methodName, Class... params) { - for (Method method : clazz.getDeclaredMethods()) { - // Limitation: Doesn't handle overloads - if ((method.getModifiers() & requireMod) == requireMod && - (method.getModifiers() & bannedMod) == 0 && - (methodName == null || method.getName().equals(methodName)) && - Arrays.equals(method.getParameterTypes(), params)) { - - method.setAccessible(true); - return method; + private static class LoadMethodSkinUpdate extends LoadCompoundMethod { + private Object readLimiter; + + public LoadMethodSkinUpdate(Class streamClass, Class readLimiterClass) { + setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class, readLimiterClass)); + + // Find the unlimited read limiter + for (Field field : readLimiterClass.getDeclaredFields()) { + if (readLimiterClass.isAssignableFrom(field.getType())) { + try { + readLimiter = field.get(null); + } catch (Exception e) { + throw new RuntimeException("Cannot retrieve read limiter.", e); + } + } } } - // Search in every superclass - if (clazz.getSuperclass() != null) - return getMethod(requireMod, bannedMod, clazz.getSuperclass(), methodName, params); - throw new IllegalStateException(String.format( - "Unable to find method %s (%s).", methodName, Arrays.asList(params))); + + @Override + public Object loadNbt(DataInput input) { + return invokeMethod(staticMethod, null, input, readLimiter); + } } - + /** - * Search for the first publically and privately defined field of the given name. - * @param instance - an instance of the class with the field. - * @param clazz - an optional class to start with, or NULL to deduce it from instance. - * @param fieldName - the field name. - * @return The first field by this name. - * @throws IllegalStateException If we cannot find this field. + * Represents a root NBT compound. + *

+ * All changes to this map will be reflected in the underlying NBT compound. Values may only be one of the following: + *

    + *
  • Primitive types
  • + *
  • {@link java.lang.String String}
  • + *
  • {@link NbtList}
  • + *
  • {@link NbtCompound}
  • + *
+ *

+ * See also: + *

    + *
  • {@link NbtFactory#createCompound()}
  • + *
  • {@link NbtFactory#fromCompound(Object)}
  • + *
+ * + * @author Kristian */ - private static Field getField(Object instance, Class clazz, String fieldName) { - if (clazz == null) - clazz = instance.getClass(); - // Ignore access rules - for (Field field : clazz.getDeclaredFields()) { - if (field.getName().equals(fieldName)) { - field.setAccessible(true); - return field; + public final class NbtCompound extends ConvertedMap { + private NbtCompound(Object handle) { + super(handle, getDataMap(handle)); + } + + // Simplifiying access to each value + public Byte getByte(String key, Byte defaultValue) { + return containsKey(key) ? (Byte) get(key) : defaultValue; + } + + public Short getShort(String key, Short defaultValue) { + return containsKey(key) ? (Short) get(key) : defaultValue; + } + + public Integer getInteger(String key, Integer defaultValue) { + return containsKey(key) ? (Integer) get(key) : defaultValue; + } + + public Long getLong(String key, Long defaultValue) { + return containsKey(key) ? (Long) get(key) : defaultValue; + } + + public Float getFloat(String key, Float defaultValue) { + return containsKey(key) ? (Float) get(key) : defaultValue; + } + + public Double getDouble(String key, Double defaultValue) { + return containsKey(key) ? (Double) get(key) : defaultValue; + } + + public String getString(String key, String defaultValue) { + return containsKey(key) ? (String) get(key) : defaultValue; + } + + public byte[] getByteArray(String key, byte[] defaultValue) { + return containsKey(key) ? (byte[]) get(key) : defaultValue; + } + + public int[] getIntegerArray(String key, int[] defaultValue) { + return containsKey(key) ? (int[]) get(key) : defaultValue; + } + + /** + * Retrieve the list by the given name. + * + * @param key - the name of the list. + * @param createNew - whether or not to create a new list if its missing. + * @return An existing list, a new list or NULL. + */ + public NbtList getList(String key, boolean createNew) { + NbtList list = (NbtList) get(key); + + if (list == null && createNew) + put(key, list = createList()); + return list; + } + + /** + * Retrieve the map by the given name. + * + * @param key - the name of the map. + * @param createNew - whether or not to create a new map if its missing. + * @return An existing map, a new map or NULL. + */ + public NbtCompound getMap(String key, boolean createNew) { + return getMap(Collections.singletonList(key), createNew); + } + // Done + + /** + * Set the value of an entry at a given location. + *

+ * Every element of the path (except the end) are assumed to be compounds, and will + * be created if they are missing. + * + * @param path - the path to the entry. + * @param value - the new value of this entry. + * @return This compound, for chaining. + */ + public NbtCompound putPath(String path, Object value) { + List entries = getPathElements(path); + Map map = getMap(entries.subList(0, entries.size() - 1), true); + + map.put(entries.get(entries.size() - 1), value); + return this; + } + + /** + * Retrieve the value of a given entry in the tree. + *

+ * Every element of the path (except the end) are assumed to be compounds. The + * retrieval operation will be cancelled if any of them are missing. + * + * @param path - path to the entry. + * @return The value, or NULL if not found. + */ + @SuppressWarnings("unchecked") + public T getPath(String path) { + List entries = getPathElements(path); + NbtCompound map = getMap(entries.subList(0, entries.size() - 1), false); + + if (map != null) { + return (T) map.get(entries.get(entries.size() - 1)); } + return null; + } + + /** + * Save the content of a NBT compound to a stream. + * + * @param stream - the output stream. + * @param option - whether or not to compress the output. + * @throws IOException If anything went wrong. + */ + public void saveTo(ByteSink stream, StreamOptions option) throws IOException { + saveStream(this, stream, option); + } + + /** + * Retrieve a map from a given path. + * + * @param path - path of compounds to look up. + * @param createNew - whether or not to create new compounds on the way. + * @return The map at this location. + */ + private NbtCompound getMap(Iterable path, boolean createNew) { + NbtCompound current = this; + + for (String entry : path) { + NbtCompound child = (NbtCompound) current.get(entry); + + if (child == null) { + if (!createNew) + return null; + current.put(entry, child = createCompound()); + } + current = child; + } + return current; + } + + /** + * Split the path into separate elements. + * + * @param path - the path to split. + * @return The elements. + */ + private List getPathElements(String path) { + return Lists.newArrayList(Splitter.on(".").omitEmptyStrings().split(path)); + } + } + + /** + * Represents a root NBT list. + * See also: + *

    + *
  • {@link NbtFactory#fromList(Object)}
  • + *
+ * + * @author Kristian + */ + public final class NbtList extends ConvertedList { + private NbtList(Object handle) { + super(handle, getDataList(handle)); } - // Recursively fild the correct field - if (clazz.getSuperclass() != null) - return getField(instance, clazz.getSuperclass(), fieldName); - throw new IllegalStateException("Unable to find field " + fieldName + " in " + instance); } - + /** * Represents a class for caching wrappers. + * * @author Kristian */ private final class CachedNativeWrapper { // Don't recreate wrapper objects private final ConcurrentMap cache = new MapMaker().weakKeys().makeMap(); - + public Object wrap(Object value) { Object current = cache.get(value); - + if (current == null) { current = wrapNative(value); - + // Only cache composite objects - if (current instanceof ConvertedMap || - current instanceof ConvertedList) { + if (current instanceof ConvertedMap || + current instanceof ConvertedList) { cache.put(value, current); } } return current; } } - + /** - * Represents a map that wraps another map and automatically + * Represents a map that wraps another map and automatically * converts entries of its type and another exposed type. + * * @author Kristian */ private class ConvertedMap extends AbstractMap implements Wrapper { private final Object handle; private final Map original; - + private final CachedNativeWrapper cache = new CachedNativeWrapper(); public ConvertedMap(Object handle, Map original) { this.handle = handle; this.original = original; } - + // For converting back and forth protected Object wrapOutgoing(Object value) { return cache.wrap(value); } + protected Object unwrapIncoming(Object wrapped) { return unwrapValue(wrapped); } - + // Modification @Override public Object put(String key, Object value) { return wrapOutgoing(original.put( - (String) key, - unwrapIncoming(value) + key, + unwrapIncoming(value) )); } - + // Performance @Override public Object get(Object key) { return wrapOutgoing(original.get(key)); } + @Override public Object remove(Object key) { return wrapOutgoing(original.remove(key)); } + @Override public boolean containsKey(Object key) { return original.containsKey(key); } - + @Override public Set> entrySet() { - return new AbstractSet>() { + return new AbstractSet>() { @Override public boolean add(Entry e) { String key = e.getKey(); Object value = e.getValue(); - + original.put(key, unwrapIncoming(value)); return true; } - + @Override public int size() { return original.size(); } - + @Override public Iterator> iterator() { return ConvertedMap.this.iterator(); } }; } - + private Iterator> iterator() { final Iterator> proxy = original.entrySet().iterator(); - + return new Iterator>() { @Override public boolean hasNext() { return proxy.hasNext(); } - + @Override public Entry next() { Entry entry = proxy.next(); - - return new SimpleEntry( - entry.getKey(), wrapOutgoing(entry.getValue()) + + return new SimpleEntry<>( + entry.getKey(), wrapOutgoing(entry.getValue()) ); } - + @Override public void remove() { proxy.remove(); } }; } - + @Override public Object getHandle() { return handle; } } - + /** - * Represents a list that wraps another list and converts elements + * Represents a list that wraps another list and converts elements * of its type and another exposed type. + * * @author Kristian */ private class ConvertedList extends AbstractList implements Wrapper { private final Object handle; - + private final List original; private final CachedNativeWrapper cache = new CachedNativeWrapper(); - + public ConvertedList(Object handle, List original) { if (NBT_LIST_TYPE == null) NBT_LIST_TYPE = getField(handle, null, "type"); @@ -915,106 +993,51 @@ public ConvertedList(Object handle, List original) { protected Object wrapOutgoing(Object value) { return cache.wrap(value); } + protected Object unwrapIncoming(Object wrapped) { return unwrapValue(wrapped); } - + @Override public Object get(int index) { return wrapOutgoing(original.get(index)); } + @Override public int size() { return original.size(); } + @Override public Object set(int index, Object element) { return wrapOutgoing( - original.set(index, unwrapIncoming(element)) + original.set(index, unwrapIncoming(element)) ); } + @Override public void add(int index, Object element) { Object nbt = unwrapIncoming(element); - + // Set the list type if its the first element - if (size() == 0) - setFieldValue(NBT_LIST_TYPE, handle, (byte)getNbtType(nbt).id); + if (size() == 0) + setFieldValue(NBT_LIST_TYPE, handle, (byte) getNbtType(nbt).id); original.add(index, nbt); } + @Override public Object remove(int index) { return wrapOutgoing(original.remove(index)); } + @Override public boolean remove(Object o) { return original.remove(unwrapIncoming(o)); } - + @Override public Object getHandle() { return handle; } } - - /** - * Represents a method for loading an NBT compound. - * @author Kristian - */ - private static abstract class LoadCompoundMethod { - protected Method staticMethod; - - protected void setMethod(Method method) { - this.staticMethod = method; - this.staticMethod.setAccessible(true); - } - - /** - * Load an NBT compound from a given stream. - * @param input - the input stream. - * @return The loaded NBT compound. - */ - public abstract Object loadNbt(DataInput input); - } - - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.2 - 1.7.5 - */ - private static class LoadMethodWorldUpdate extends LoadCompoundMethod { - public LoadMethodWorldUpdate(Class streamClass) { - setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class)); - } - - @Override - public Object loadNbt(DataInput input) { - return invokeMethod(staticMethod, null, input); - } - } - - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.8 - */ - private static class LoadMethodSkinUpdate extends LoadCompoundMethod { - private Object readLimiter; - - public LoadMethodSkinUpdate(Class streamClass, Class readLimiterClass) { - setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class, readLimiterClass)); - - // Find the unlimited read limiter - for (Field field : readLimiterClass.getDeclaredFields()) { - if (readLimiterClass.isAssignableFrom(field.getType())) { - try { - readLimiter = field.get(null); - } catch (Exception e) { - throw new RuntimeException("Cannot retrieve read limiter.", e); - } - } - } - } - - @Override - public Object loadNbt(DataInput input) { - return invokeMethod(staticMethod, null, input, readLimiter); - } - } } \ No newline at end of file