diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..916e17c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dependency-reduced-pom.xml diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml deleted file mode 100644 index ea37303c..00000000 --- a/ProtocolLib/dependency-reduced-pom.xml +++ /dev/null @@ -1,218 +0,0 @@ - - - 4.0.0 - com.comphenix.protocol - ProtocolLib - ProtocolLib - 1.9.0 - Provides read/write access to the Minecraft protocol. - http://dev.bukkit.org/server-mods/protocollib/ - - - aadnk - Kristian S. Stangeland - kr_stang@hotmail.com - http://comphenix.net/ - - developer - maintainer - - 1 - - - - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - http://www.gnu.org/licenses/gpl-2.0.txt - repo - - - - scm:git:git://github.com/aadnk/ProtocolLib.git - scm:git:git@github.com:aadnk/ProtocolLib.git - https://github.com/aadnk/ProtocolLib - - - src/main/java - src/test/java - clean install - - - src/main/resources - - **/*.java - - - - - - maven-shade-plugin - 2.0 - - - package - - shade - - - false - true - - - net.sf - com.comphenix.net.sf - - - - - org.bukkit:craftbukkit - junit:junit - - - - - - - - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - - - release-sign-artifacts - - - - maven-source-plugin - 2.2 - - - attach-sources - - jar - - - - - - maven-javadoc-plugin - 2.9 - - - attach-javadocs - - jar - - - - - - maven-gpg-plugin - 1.4 - - - sign-artifacts - verify - - sign - - - - - - - - - - - bukkit-rep - http://repo.bukkit.org/content/groups/public - - - - - org.bukkit - craftbukkit - 1.4.6-R0.1 - provided - - - junit - junit - 4.10 - test - - - hamcrest-core - org.hamcrest - - - - - org.mockito - mockito-all - 1.8.4 - test - - - org.powermock - powermock-module-junit4 - 1.5 - test - - - powermock-module-junit4-common - org.powermock - - - - - org.powermock - powermock-api-mockito - 1.5 - test - - - powermock-api-support - org.powermock - - - - - - - - org.codehaus.mojo - findbugs-maven-plugin - 2.5.2 - - High - Default - - - - - - - comphenix-releases - Comphenix Maven Releases - http://repo.comphenix.net/content/repositories/releases/ - - - comphenix-snapshots - Comphenix Maven Snapshots - http://repo.comphenix.net/content/repositories/snapshots/ - - - - 1.5 - cp1252 - - - diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index c95ebec1..a81c34d1 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.9.0 + 2.0.0 jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index c724a266..4c679cb3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -51,10 +51,12 @@ import com.comphenix.protocol.reflect.cloning.ImmutableDetector; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.google.common.base.Function; import com.google.common.collect.Maps; @@ -235,6 +237,14 @@ public class PacketContainer implements Serializable { public StructureModifier getByteArrays() { return structureModifier.withType(byte[].class); } + + /** + * Retrieve a serializer for reading and writing ItemStacks stored in a byte array. + * @return A instance of the serializer. + */ + public StreamSerializer getByteArraySerializer() { + return new StreamSerializer(); + } /** * Retrieves a read/write structure for every int array field. @@ -355,6 +365,17 @@ public class PacketContainer implements Serializable { ChunkPosition.getConverter()); } + /** + * Retrieves a read/write structure for NBT classes. + * @return A modifier for NBT classes. + */ + public StructureModifier> getNbtModifier() { + // Allow access to the NBT class in packet 130 + return structureModifier.withType( + MinecraftReflection.getNBTBaseClass(), + BukkitConverters.getNbtConverter()); + } + /** * Retrieves a read/write structure for collections of chunk positions. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java index 3ee7d6e9..3ff786ff 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Metrics.java @@ -70,85 +70,73 @@ import java.util.UUID; import java.util.logging.Level; /** - *

- * The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. - *

- *

- * Public methods provided by this class: - *

+ *

The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.

+ * Public methods provided by this class:

* * Graph createGraph(String name);
- * void addCustomData(Metrics.Plotter plotter);
+ * void addCustomData(BukkitMetrics.Plotter plotter);
* void start();
*
*/ -class Metrics { +public class Metrics { /** * The current revision number */ - private final static int REVISION = 5; - + private final static int REVISION = 6; /** * The base url of the metrics domain */ private static final String BASE_URL = "http://mcstats.org"; - /** * The url used to report a server's status */ private static final String REPORT_URL = "/report/%s"; - /** - * The separator to use for custom data. This MUST NOT change unless you are hosting your own - * version of metrics and want to change it. + * The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and + * want to change it. */ private static final String CUSTOM_DATA_SEPARATOR = "~~"; - /** * Interval of time to ping (in minutes) */ private static final int PING_INTERVAL = 10; - /** * The plugin this metrics submits for */ private final Plugin plugin; - /** * All of the custom graphs to submit to metrics */ private final Set graphs = Collections.synchronizedSet(new HashSet()); - /** * The default graph, used for addCustomData when you don't want a specific graph */ private final Graph defaultGraph = new Graph("Default"); - /** * The plugin configuration file */ private final YamlConfiguration configuration; - /** * The plugin configuration file */ private final File configurationFile; - /** * Unique server id */ private final String guid; - + /** + * Debug mode + */ + private final boolean debug; /** * Lock for synchronization */ private final Object optOutLock = new Object(); - /** - * Id of the scheduled task + * The scheduled task */ - private volatile int taskId = -1; + private volatile Scheduling.TaskWrapper task = null; public Metrics(final Plugin plugin) throws IOException { if (plugin == null) { @@ -164,6 +152,7 @@ class Metrics { // add some defaults configuration.addDefault("opt-out", false); configuration.addDefault("guid", UUID.randomUUID().toString()); + configuration.addDefault("debug", false); // Do we need to create the file? if (configuration.get("guid", null) == null) { @@ -173,11 +162,12 @@ class Metrics { // Load the guid then guid = configuration.getString("guid"); + debug = configuration.getBoolean("debug", false); } /** - * Construct and create a Graph that can be used to separate specific plotters to their own graphs - * on the metrics website. Plotters can be added to the graph object returned. + * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics + * website. Plotters can be added to the graph object returned. * * @param name The name of the graph * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given @@ -198,7 +188,7 @@ class Metrics { } /** - * Add a Graph object to Metrics that represents data for the plugin that should be sent to the backend + * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend * * @param graph The name of the graph */ @@ -228,14 +218,13 @@ class Metrics { } /** - * Start measuring statistics. This will immediately create an async repeating task as the plugin and send - * the initial data to the metrics backend, and then after that it will post in increments of - * PING_INTERVAL * 1200 ticks. + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the + * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200 + * ticks. * * @return True if statistics measuring is running, otherwise false. */ - @SuppressWarnings("deprecation") - public boolean start() { + public boolean start() { synchronized (optOutLock) { // Did we opt out? if (isOptOut()) { @@ -243,12 +232,12 @@ class Metrics { } // Is metrics already running? - if (taskId >= 0) { + if (task != null) { return true; } // Begin hitting the server with glorious data - taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { + task = Scheduling.runAsynchronously(plugin, new Runnable() { private boolean firstPost = true; @@ -257,11 +246,11 @@ class Metrics { // This has to be synchronized or it can collide with the disable method. synchronized (optOutLock) { // Disable Task, if it is running and the server owner decided to opt-out - if (isOptOut() && taskId > 0) { - plugin.getServer().getScheduler().cancelTask(taskId); - taskId = -1; + if (isOptOut() && task != null) { + task.cancel(); + task = null; // Tell all plotters to stop gathering information. - for (Graph graph : graphs){ + for (Graph graph : graphs) { graph.onOptOut(); } } @@ -276,7 +265,9 @@ class Metrics { // Each post thereafter will be a ping firstPost = false; } catch (IOException e) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } } } }, 0, PING_INTERVAL * 1200); @@ -291,15 +282,19 @@ class Metrics { * @return true if metrics should be opted out of it */ public boolean isOptOut() { - synchronized(optOutLock) { + synchronized (optOutLock) { try { // Reload the metrics file configuration.load(getConfigFile()); } catch (IOException ex) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } return true; } catch (InvalidConfigurationException ex) { - Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + if (debug) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + } return true; } return configuration.getBoolean("opt-out", false); @@ -307,30 +302,30 @@ class Metrics { } /** - * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. - * - * @throws IOException - */ + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws java.io.IOException + */ public void enable() throws IOException { // This has to be synchronized or it can collide with the check in the task. synchronized (optOutLock) { - // Check if the server owner has already set opt-out, if not, set it. - if (isOptOut()) { - configuration.set("opt-out", false); - configuration.save(configurationFile); - } + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } - // Enable Task, if it is not running - if (taskId < 0) { - start(); - } + // Enable Task, if it is not running + if (task == null) { + start(); + } } } /** * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. * - * @throws IOException + * @throws java.io.IOException */ public void disable() throws IOException { // This has to be synchronized or it can collide with the check in the task. @@ -342,9 +337,9 @@ class Metrics { } // Disable Task, if it is running - if (taskId > 0) { - this.plugin.getServer().getScheduler().cancelTask(taskId); - taskId = -1; + if (task != null) { + task.cancel(); + task = null; } } } @@ -370,17 +365,45 @@ class Metrics { * Generic method that posts a plugin to the metrics website */ private void postPlugin(final boolean isPing) throws IOException { - // The plugin's description file containg all of the plugin data such as name, version, author, etc - final PluginDescriptionFile description = plugin.getDescription(); + // Server software specific section + PluginDescriptionFile description = plugin.getDescription(); + String pluginName = description.getName(); + boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled + String pluginVersion = description.getVersion(); + String serverVersion = Bukkit.getVersion(); + int playersOnline = Bukkit.getServer().getOnlinePlayers().length; + + // END server software specific section -- all code below does not use any code outside of this class / Java // Construct the post data final StringBuilder data = new StringBuilder(); + + // The plugin's description file containg all of the plugin data such as name, version, author, etc data.append(encode("guid")).append('=').append(encode(guid)); - encodeDataPair(data, "version", description.getVersion()); - encodeDataPair(data, "server", Bukkit.getVersion()); - encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); + encodeDataPair(data, "version", pluginVersion); + encodeDataPair(data, "server", serverVersion); + encodeDataPair(data, "players", Integer.toString(playersOnline)); encodeDataPair(data, "revision", String.valueOf(REVISION)); + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + + encodeDataPair(data, "osname", osname); + encodeDataPair(data, "osarch", osarch); + encodeDataPair(data, "osversion", osversion); + encodeDataPair(data, "cores", Integer.toString(coreCount)); + encodeDataPair(data, "online-mode", Boolean.toString(onlineMode)); + encodeDataPair(data, "java_version", java_version); + // If we're pinging, append it if (isPing) { encodeDataPair(data, "ping", "true"); @@ -411,7 +434,7 @@ class Metrics { } // Create the url - URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(pluginName))); // Connect to the website URLConnection connection; @@ -474,8 +497,8 @@ class Metrics { } /** - *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first - * key/value pair MUST be included manually, e.g:

+ *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair + * MUST be included manually, e.g:

* * StringBuffer data = new StringBuffer(); * data.append(encode("guid")).append('=').append(encode(guid)); @@ -506,11 +529,10 @@ class Metrics { public static class Graph { /** - * The graph's name, alphanumeric and spaces only :) - * If it does not comply to the above when submitted, it is rejected + * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is + * rejected */ private final String name; - /** * The set of plotters that are contained within this graph */ @@ -550,7 +572,7 @@ class Metrics { /** * Gets an unmodifiable set of the plotter objects in the graph * - * @return an unmodifiable {@link Set} of the plotter objects + * @return an unmodifiable {@link java.util.Set} of the plotter objects */ public Set getPlotters() { return Collections.unmodifiableSet(plotters); @@ -572,11 +594,10 @@ class Metrics { } /** - * Called when the server owner decides to opt-out of Metrics while the server is running. + * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. */ protected void onOptOut() { } - } /** @@ -606,10 +627,9 @@ class Metrics { } /** - * Get the current value for the plotted point. Since this function defers to an external function - * it may or may not return immediately thus cannot be guaranteed to be thread friendly or safe. - * This function can be called from any thread so care should be taken when accessing resources - * that need to be synchronized. + * Get the current value for the plotted point. Since this function defers to an external function it may or may + * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called + * from any thread so care should be taken when accessing resources that need to be synchronized. * * @return the current value for the point to be plotted. */ @@ -644,6 +664,5 @@ class Metrics { final Plotter plotter = (Plotter) object; return plotter.name.equals(name) && plotter.getValue() == getValue(); } - } } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Scheduling.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Scheduling.java new file mode 100644 index 00000000..42e6db77 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Scheduling.java @@ -0,0 +1,81 @@ +package com.comphenix.protocol.metrics; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; + +/** + * Allows us to stay backwards compatible with older versions of Bukkit. + * + * @author Kristian + */ +class Scheduling { + /** + * Represents a backwards compatible Bukkit task. + */ + public static interface TaskWrapper { + /** + * Cancel the current task. + */ + public void cancel(); + } + + /** + * Schedule a given task for asynchronous execution. + * @param plugin - the owner plugin. + * @param runnable - the task to run. + * @param firstDelay - the amount of time to wait until executing the task for the first time. + * @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once. + * @return A cancel token. + */ + public static TaskWrapper runAsynchronously(final Plugin plugin, Runnable runnable, long firstDelay, long repeatDelay) { + return runAsynchronously(plugin, plugin.getServer().getScheduler(), runnable, firstDelay, repeatDelay); + } + + /** + * Schedule a given task for asynchronous execution. + * @param plugin - the owner plugin. + * @param scheduler - the current Bukkit scheduler. + * @param runnable - the task to run. + * @param firstDelay - the amount of time to wait until executing the task for the first time. + * @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once. + * @return A cancel token. + */ + public static TaskWrapper runAsynchronously(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) { + try { + @SuppressWarnings("deprecation") + final int taskID = scheduler.scheduleAsyncRepeatingTask(plugin, runnable, firstDelay, repeatDelay); + + // Return the cancellable object + return new TaskWrapper() { + @Override + public void cancel() { + scheduler.cancelTask(taskID); + } + }; + + } catch (NoSuchMethodError e) { + return tryUpdatedVersion(plugin, scheduler, runnable, firstDelay, repeatDelay); + } + } + + /** + * Attempt to do the same with the updated scheduling method. + * @param plugin - the owner plugin. + * @param scheduler - the current Bukkit scheduler. + * @param runnable - the task to run. + * @param firstDelay - the amount of time to wait until executing the task for the first time. + * @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once. + * @return A cancel token. + */ + private static TaskWrapper tryUpdatedVersion(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) { + final BukkitTask task = scheduler.runTaskTimerAsynchronously(plugin, runnable, firstDelay, repeatDelay); + + return new TaskWrapper() { + @Override + public void cancel() { + task.cancel(); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java index ad285df8..f33fb1b2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -37,6 +37,20 @@ public class PrettyPrinter { */ public final static int RECURSE_DEPTH = 3; + /** + * Print the content of an object. + * @param object - the object to serialize. + * @param stop - superclass that will stop the process. + * @return String representation of the class. + * @throws IllegalAccessException + */ + public static String printObject(Object object) throws IllegalAccessException { + if (object == null) + throw new IllegalArgumentException("object cannot be NULL."); + + return printObject(object, object.getClass(), Object.class); + } + /** * Print the content of an object. * @param object - the object to serialize. @@ -45,6 +59,9 @@ public class PrettyPrinter { * @throws IllegalAccessException */ public static String printObject(Object object, Class start, Class stop) throws IllegalAccessException { + if (object == null) + throw new IllegalArgumentException("object cannot be NULL."); + return printObject(object, start, stop, RECURSE_DEPTH); } @@ -56,6 +73,9 @@ public class PrettyPrinter { * @throws IllegalAccessException */ public static String printObject(Object object, Class start, Class stop, int hierachyDepth) throws IllegalAccessException { + if (object == null) + throw new IllegalArgumentException("object cannot be NULL."); + StringBuilder output = new StringBuilder(); Set previous = new HashSet(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java index 6f636c49..4359e080 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java @@ -24,8 +24,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; @@ -150,41 +148,59 @@ public class BackgroundCompiler { if (executor == null || executor.isShutdown()) return; - try { - executor.submit(new Callable() { - @Override - public Object call() throws Exception { - StructureModifier modifier = uncompiled; - - // Do our compilation - try { - modifier = compiler.compile(modifier); - listener.onCompiled(modifier); + // Create the worker that will compile our modifier + Callable worker = new Callable() { + @Override + public Object call() throws Exception { + StructureModifier modifier = uncompiled; + + // Do our compilation + try { + modifier = compiler.compile(modifier); + listener.onCompiled(modifier); - } catch (Throwable e) { - // Disable future compilations! - setEnabled(false); - - // Inform about this error as best as we can - if (reporter != null) { - reporter.reportDetailed(BackgroundCompiler.this, - "Cannot compile structure. Disabing compiler.", e, uncompiled); - } else { - System.err.println("Exception occured in structure compiler: "); - e.printStackTrace(); - } + } catch (Throwable e) { + // Disable future compilations! + setEnabled(false); + + // Inform about this error as best as we can + if (reporter != null) { + reporter.reportDetailed(BackgroundCompiler.this, + "Cannot compile structure. Disabing compiler.", e, uncompiled); + } else { + System.err.println("Exception occured in structure compiler: "); + e.printStackTrace(); } - - // We'll also return the new structure modifier - return modifier; - } - }); + + // We'll also return the new structure modifier + return modifier; + + } + }; + + try { + // Lookup the previous class name on the main thread. + // This is necessary as the Bukkit class loaders are not thread safe + if (compiler.lookupClassLoader(uncompiled)) { + try { + worker.call(); + } catch (Exception e) { + // Impossible! + e.printStackTrace(); + } + + } else { + + // Perform the compilation on a seperate thread + executor.submit(worker); + } + } catch (RejectedExecutionException e) { // Occures when the underlying queue is overflowing. Since the compilation // is only an optmization and not really essential we'll just log this failure // and move on. - Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e); + reporter.reportWarning(this, "Unable to schedule compilation task.", e); } } } @@ -209,7 +225,7 @@ public class BackgroundCompiler { try { executor.awaitTermination(timeout, unit); } catch (InterruptedException e) { - // Unlikely to ever occur. + // Unlikely to ever occur - it's the main thread e.printStackTrace(); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index cf25ebe7..579c069a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -21,9 +21,9 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.comphenix.protocol.reflect.StructureModifier; import com.google.common.base.Objects; @@ -98,6 +98,10 @@ public final class StructureCompiler { private Class targetType; private Class fieldType; + public StructureKey(StructureModifier source) { + this(source.getTargetType(), source.getFieldType()); + } + public StructureKey(Class targetType, Class fieldType) { this.targetType = targetType; this.fieldType = fieldType; @@ -123,7 +127,7 @@ public final class StructureCompiler { private volatile static Method defineMethod; @SuppressWarnings("rawtypes") - private Map compiledCache = new HashMap(); + private Map compiledCache = new ConcurrentHashMap(); // The class loader we'll store our classes private ClassLoader loader; @@ -142,6 +146,37 @@ public final class StructureCompiler { this.loader = loader; } + /** + * Lookup the current class loader for any previously generated classes before we attempt to generate something. + * @param source - the structure modifier to look up. + * @return TRUE if we successfully found a previously generated class, FALSE otherwise. + */ + public boolean lookupClassLoader(StructureModifier source) { + StructureKey key = new StructureKey(source); + + // See if there's a need to lookup the class name + if (compiledCache.containsKey(key)) { + return true; + } + + try { + String className = getCompiledName(source); + + // This class might have been generated before. Try to load it. + Class before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className); + + if (before != null) { + compiledCache.put(key, before); + return true; + } + } catch (ClassNotFoundException e) { + // That's ok. + } + + // We need to compile the class + return false; + } + /** * Compiles the given structure modifier. *

@@ -158,7 +193,7 @@ public final class StructureCompiler { return source; } - StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType()); + StructureKey key = new StructureKey(source); Class compiledClass = compiledCache.get(key); if (!compiledCache.containsKey(key)) { @@ -195,29 +230,35 @@ public final class StructureCompiler { return type.getCanonicalName().replace("[]", "Array").replace(".", "_"); } + /** + * Retrieve the compiled name of a given structure modifier. + * @param source - the structure modifier. + * @return The unique, compiled name of a compiled structure modifier. + */ + private String getCompiledName(StructureModifier source) { + Class targetType = source.getTargetType(); + + // Concat class and field type + return "CompiledStructure$" + + getSafeTypeName(targetType) + "$" + + getSafeTypeName(source.getFieldType()); + } + + /** + * Compile a structure modifier. + * @param source - structure modifier. + * @return The compiled structure modifier. + */ private Class generateClass(StructureModifier source) { ClassWriter cw = new ClassWriter(0); - - @SuppressWarnings("rawtypes") - Class targetType = source.getTargetType(); + Class targetType = source.getTargetType(); - String className = "CompiledStructure$" + - getSafeTypeName(targetType) + "$" + - getSafeTypeName(source.getFieldType()); + String className = getCompiledName(source); String targetSignature = Type.getDescriptor(targetType); String targetName = targetType.getName().replace('.', '/'); - try { - // This class might have been generated before. Try to load it. - Class before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className); - - if (before != null) - return before; - } catch (ClassNotFoundException e) { - // That's ok. - } - + // Define class cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, null, COMPILED_CLASS, null); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 31094d57..f9924a3a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -376,6 +376,14 @@ public class MinecraftReflection { return getMinecraftClass("WatchableObject"); } + /** + * Retrieve the NBT base class. + * @return The NBT base class. + */ + public static Class getNBTBaseClass() { + return getMinecraftClass("NBTBase"); + } + /** * Retrieve the ItemStack[] class. * @return The ItemStack[] class. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java new file mode 100644 index 00000000..ac4fe360 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -0,0 +1,103 @@ +package com.comphenix.protocol.utility; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import org.bukkit.inventory.ItemStack; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import com.comphenix.protocol.reflect.FuzzyReflection; + +/** + * Utility methods for reading and writing Minecraft objects to streams. + * + * @author Kristian + */ +public class StreamSerializer { + // Cached methods + private static Method readItemMethod; + private static Method writeItemMethod; + + /** + * Read or deserialize an item stack from an underlying input stream. + *

+ * To supply a byte array, wrap it in a {@link java.io.ByteArrayInputStream ByteArrayInputStream} + * and {@link java.io.DataInputStream DataInputStream}. + * + * @param input - the target input stream. + * @return The resulting item stack. + * @throws IOException If the operation failed due to reflection or corrupt data. + */ + public ItemStack deserializeItemStack(DataInputStream input) throws IOException { + if (readItemMethod == null) + readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). + getMethodByParameters("readPacket", + MinecraftReflection.getItemStackClass(), + new Class[] {DataInputStream.class}); + try { + Object nmsItem = readItemMethod.invoke(null, input); + + // Convert back to a Bukkit item stack + return MinecraftReflection.getBukkitItemStack(nmsItem); + + } catch (Exception e) { + throw new IOException("Cannot read item stack.", e); + } + } + + /** + * Deserialize an item stack from a base-64 encoded string. + * @param input - base-64 encoded string. + * @return A deserialized item stack. + * @throws IOException If the operation failed due to reflection or corrupt data. + */ + public ItemStack deserializeItemStack(String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input)); + + return deserializeItemStack(new DataInputStream(inputStream)); + } + + /** + * Write or serialize an item stack to the given output stream. + *

+ * To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream} + * and {@link java.io.DataOutputStream DataOutputStream}. + * + * @param output - the target output stream. + * @param stack - the item stack that will be written. + * @throws IOException If the operation fails due to reflection problems. + */ + public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException { + Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); + + if (writeItemMethod == null) + writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). + getMethodByParameters("writePacket", new Class[] { + MinecraftReflection.getItemStackClass(), + DataOutputStream.class }); + try { + writeItemMethod.invoke(null, nmsItem, output); + } catch (Exception e) { + throw new IOException("Cannot write item stack " + stack, e); + } + } + + /** + * Serialize an item stack as a base-64 encoded string. + * @param stack - the item stack to serialize. + * @return A base-64 representation of the given item stack. + * @throws IOException If the operation fails due to reflection problems. + */ + public String serializeItemStack(ItemStack stack) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutput = new DataOutputStream(outputStream); + + serializeItemStack(dataOutput, stack); + + // Serialize that array + return Base64Coder.encodeLines(outputStream.toByteArray()); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 7d6f421e..95606336 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -34,6 +34,8 @@ import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; /** * Contains several useful equivalent converters for normal Bukkit types. @@ -208,6 +210,32 @@ public class BukkitConverters { }); } + /** + * Retrieve an equivalent converter for net.minecraft.server NBT classes and their wrappers. + * @return An equivalent converter for NBT. + */ + public static EquivalentConverter> getNbtConverter() { + return getIgnoreNull(new EquivalentConverter>() { + @Override + public Object getGeneric(Class genericType, NbtBase specific) { + return NbtFactory.fromBase(specific).getHandle(); + } + + @Override + public NbtBase getSpecific(Object generic) { + return NbtFactory.fromNMS(generic); + } + + @Override + @SuppressWarnings("unchecked") + public Class> getSpecificType() { + // Damn you Java AGAIN + Class dummy = NbtBase.class; + return (Class>) dummy; + } + }); + } + /** * Retrieve a converter for NMS entities and Bukkit entities. * @param world - the current world. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java new file mode 100644 index 00000000..554d4c6b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/AbstractConverted.java @@ -0,0 +1,59 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import javax.annotation.Nullable; + +import com.google.common.base.Function; + +/** + * Represents an object that transform elements of type VInner to type VOuter and back again. + * + * @author Kristian + * + * @param - the first type. + * @param - the second type. + */ +abstract class AbstractConverted { + /** + * Convert a value from the inner map to the outer visible map. + * @param inner - the inner value. + * @return The outer value. + */ + protected abstract VOuter toOuter(VInner inner); + + /** + * Convert a value from the outer map to the internal inner map. + * @param outer - the outer value. + * @return The inner value. + */ + protected abstract VInner toInner(VOuter outer); + + /** + * Retrieve a function delegate that converts inner objects to outer objects. + * @return A function delegate. + */ + protected Function getOuterConverter() { + return new Function() { + @Override + public VOuter apply(@Nullable VInner param) { + return toOuter(param); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java new file mode 100644 index 00000000..dea203e6 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedCollection.java @@ -0,0 +1,144 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; + +/** + * Represents a collection that wraps another collection by transforming the elements going in and out. + * + * @author Kristian + * + * @param - type of the element in the inner invisible collection. + * @param - type of the elements publically accessible in the outer collection. + */ +abstract class ConvertedCollection extends AbstractConverted implements Collection { + // Inner collection + private Collection inner; + + public ConvertedCollection(Collection inner) { + this.inner = inner; + } + + @Override + public boolean add(VOuter e) { + return inner.add(toInner(e)); + } + + @Override + public boolean addAll(Collection c) { + boolean modified = false; + + for (VOuter outer : c) + modified |= add(outer); + return modified; + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + return inner.contains(toInner((VOuter) o)); + } + + @Override + public boolean containsAll(Collection c) { + for (Object outer : c) { + if (!contains(outer)) + return false; + } + return true; + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public Iterator iterator() { + return Iterators.transform(inner.iterator(), getOuterConverter()); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + return inner.remove(toInner((VOuter) o)); + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + + for (Object outer : c) + modified |= remove(outer); + return modified; + } + + @Override + @SuppressWarnings("unchecked") + public boolean retainAll(Collection c) { + List innerCopy = Lists.newArrayList(); + + // Convert all the elements + for (Object outer : c) + innerCopy.add(toInner((VOuter) outer)); + return inner.retainAll(innerCopy); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + @SuppressWarnings("unchecked") + public Object[] toArray() { + Object[] array = inner.toArray(); + + for (int i = 0; i < array.length; i++) + array[i] = toOuter((VInner) array[i]); + return array; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + T[] array = a; + int index = 0; + + if (array.length < size()) { + array = (T[]) Array.newInstance(a.getClass().getComponentType(), size()); + } + + // Build the output array + for (VInner innerValue : inner) + array[index++] = (T) toOuter(innerValue); + return array; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java new file mode 100644 index 00000000..3562a7c5 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedList.java @@ -0,0 +1,163 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +/** + * Represents a list that wraps another list by transforming the items going in and out. + * + * @author Kristian + * + * @param - type of the items in the inner invisible list. + * @param - type of the items publically accessible in the outer list. + */ +abstract class ConvertedList extends ConvertedCollection implements List { + private List inner; + + public ConvertedList(List inner) { + super(inner); + this.inner = inner; + } + + @Override + public void add(int index, VOuter element) { + inner.add(index, toInner(element)); + } + + @Override + public boolean addAll(int index, Collection c) { + return inner.addAll(index, getInnerCollection(c)); + } + + @Override + public VOuter get(int index) { + return toOuter(inner.get(index)); + } + + @Override + @SuppressWarnings("unchecked") + public int indexOf(Object o) { + return inner.indexOf(toInner((VOuter) o)); + } + + @Override + @SuppressWarnings("unchecked") + public int lastIndexOf(Object o) { + return inner.lastIndexOf(toInner((VOuter) o)); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(int index) { + final ListIterator innerIterator = inner.listIterator(index); + + return new ListIterator() { + @Override + public void add(VOuter e) { + innerIterator.add(toInner(e)); + } + + @Override + public boolean hasNext() { + return innerIterator.hasNext(); + } + + @Override + public boolean hasPrevious() { + return innerIterator.hasPrevious(); + } + + @Override + public VOuter next() { + return toOuter(innerIterator.next()); + } + + @Override + public int nextIndex() { + return innerIterator.nextIndex(); + } + + @Override + public VOuter previous() { + return toOuter(innerIterator.previous()); + } + + @Override + public int previousIndex() { + return innerIterator.previousIndex(); + } + + @Override + public void remove() { + innerIterator.remove(); + } + + @Override + public void set(VOuter e) { + innerIterator.set(toInner(e)); + } + }; + } + + @Override + public VOuter remove(int index) { + return toOuter(inner.remove(index)); + } + + @Override + public VOuter set(int index, VOuter element) { + return toOuter(inner.set(index, toInner(element))); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return new ConvertedList(inner.subList(fromIndex, toIndex)) { + @Override + protected VInner toInner(VOuter outer) { + return ConvertedList.this.toInner(outer); + } + + @Override + protected VOuter toOuter(VInner inner) { + return ConvertedList.this.toOuter(inner); + } + }; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private ConvertedCollection getInnerCollection(Collection c) { + return new ConvertedCollection(c) { + @Override + protected VOuter toInner(VInner outer) { + return ConvertedList.this.toOuter(outer); + } + + @Override + protected VInner toOuter(VOuter inner) { + return ConvertedList.this.toInner(inner); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java new file mode 100644 index 00000000..b662d0da --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedMap.java @@ -0,0 +1,164 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Represents a map that wraps another map by transforming the entries going in and out. + * + * @author Kristian + * + * @param - type of the value in the entries in the inner invisible map. + * @param - type of the value in the entries publically accessible in the outer map. + */ +abstract class ConvertedMap extends AbstractConverted implements Map { + // Inner map + private Map inner; + + public ConvertedMap(Map inner) { + if (inner == null) + throw new IllegalArgumentException("Inner map cannot be NULL."); + this.inner = inner; + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + public boolean containsKey(Object key) { + return inner.containsKey(key); + } + + @Override + @SuppressWarnings("unchecked") + public boolean containsValue(Object value) { + return inner.containsValue(toInner((VOuter) value)); + } + + @Override + public Set> entrySet() { + return new ConvertedSet, Entry>(inner.entrySet()) { + @Override + protected Entry toInner(final Entry outer) { + return new Entry() { + @Override + public Key getKey() { + return outer.getKey(); + } + + @Override + public VInner getValue() { + return ConvertedMap.this.toInner(outer.getValue()); + } + + @Override + public VInner setValue(VInner value) { + return ConvertedMap.this.toInner(outer.setValue(ConvertedMap.this.toOuter(value))); + } + + @Override + public String toString() { + return String.format("\"%s\": %s", getKey(), getValue()); + } + }; + } + + @Override + protected Entry toOuter(final Entry inner) { + return new Entry() { + @Override + public Key getKey() { + return inner.getKey(); + } + + @Override + public VOuter getValue() { + return ConvertedMap.this.toOuter(inner.getValue()); + } + + @Override + public VOuter setValue(VOuter value) { + return ConvertedMap.this.toOuter(inner.setValue(ConvertedMap.this.toInner(value))); + } + + @Override + public String toString() { + return String.format("\"%s\": %s", getKey(), getValue()); + } + }; + } + }; + } + + @Override + public VOuter get(Object key) { + return toOuter(inner.get(key)); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public Set keySet() { + return inner.keySet(); + } + + @Override + public VOuter put(Key key, VOuter value) { + return toOuter(inner.put(key, toInner(value))); + } + + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public VOuter remove(Object key) { + return toOuter(inner.remove(key)); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + public Collection values() { + return new ConvertedCollection(inner.values()) { + @Override + protected VOuter toOuter(VInner inner) { + return ConvertedMap.this.toOuter(inner); + } + + @Override + protected VInner toInner(VOuter outer) { + return ConvertedMap.this.toInner(outer); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java new file mode 100644 index 00000000..5aa0d203 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/ConvertedSet.java @@ -0,0 +1,35 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Collection; +import java.util.Set; + +/** + * Represents a set that wraps another set by transforming the items going in and out. + * + * @author Kristian + * + * @param - type of the element in the inner invisible set. + * @param - type of the elements publically accessible in the outer set. + */ +abstract class ConvertedSet extends ConvertedCollection implements Set { + public ConvertedSet(Collection inner) { + super(inner); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java new file mode 100644 index 00000000..4fda61b8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtBase.java @@ -0,0 +1,86 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtType; + +/** + * Represents a generic container for an NBT element. + *

+ * Use {@link NbtFactory} to load or create an instance. + * + * @author Kristian + * @param - type of the value that is stored. + */ +public interface NbtBase { + /** + * Accepts a NBT visitor. + * @param visitor - the hierarchical NBT visitor. + * @return TRUE if the parent should continue processing children at the current level, FALSE otherwise. + */ + public abstract boolean accept(NbtVisitor visitor); + + /** + * Retrieve the type of this NBT element. + * @return The type of this NBT element. + */ + public abstract NbtType getType(); + + /** + * Retrieve the name of this NBT tag. + *

+ * This will be an empty string if the NBT tag is stored in a list. + * @return Name of the tag. + */ + public abstract String getName(); + + /** + * Set the name of this NBT tag. + *

+ * This will be ignored if the NBT tag is stored in a list. + * @param name - name of the tag. + */ + public abstract void setName(String name); + + /** + * Retrieve the value of this NBT tag. + *

+ * Is either a primitive {@link java.lang.Number wrapper}, {@link java.lang.String String}, + * {@link java.util.List List} or a {@link java.util.Map Map}. + *

+ * All operations that modify collections directly, such as {@link java.util.List#add(Object) List.add(Object)} or + * {@link java.util.Map#clear() Map.clear()}, are considered optional. This also include members in {@link java.util.Iterator Iterator} and + * {@link java.util.ListIterator ListIterator}. Operations that are not implemented throw a + * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}. + * @return Value of this tag. + */ + public abstract TType getValue(); + + /** + * Set the value of this NBT tag. + * @param newValue - the new value of this tag. + */ + public abstract void setValue(TType newValue); + + /** + * Clone the current NBT tag. + * @return The cloned tag. + */ + public abstract NbtBase deepClone(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java new file mode 100644 index 00000000..ba27a6cb --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtCompound.java @@ -0,0 +1,312 @@ +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Represents a mapping of arbitrary NBT elements and their unique names. + *

+ * Use {@link NbtFactory} to load or create an instance. + *

+ * The {@link NbtBase#getValue()} method returns a {@link java.util.Map} that will return the full content + * of this NBT compound, but may throw an {@link UnsupportedOperationException} for any of the write operations. + * + * @author Kristian + */ +public interface NbtCompound extends NbtBase>>, Iterable> { + /** + * Determine if an entry with the given key exists or not. + * @param key - the key to lookup. + * @return TRUE if an entry with the given key exists, FALSE otherwise. + */ + public abstract boolean containsKey(String key); + + /** + * Retrieve a Set view of the keys of each entry in this compound. + * @return The keys of each entry. + */ + public abstract Set getKeys(); + + /** + * Retrieve the value of a given entry. + * @param key - key of the entry to retrieve. + * @return The value of this entry, or NULL if not found. + */ + public abstract NbtBase getValue(String key); + + /** + * Retrieve a value by its key, or assign and return a new NBT element if it doesn't exist. + * @param key - the key of the entry to find or create. + * @param type - the NBT element we will create if not found. + * @return The value that was retrieved or just created. + */ + public abstract NbtBase getValueOrDefault(String key, NbtType type); + + /** + * Set a entry based on its name. + * @param entry - entry with a name and value. + * @return This compound, for chaining. + */ + public abstract NbtCompound put(NbtBase entry); + + /** + * Retrieve the string value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The string value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract String getString(String key); + + /** + * Retrieve the string value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract String getStringOrDefault(String key); + + /** + * Associate a NBT string value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, String value); + + /** + * Inserts an entry after cloning it and renaming it to "key". + * @param key - the name of the entry. + * @param entry - the entry to insert. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, NbtBase entry); + + /** + * Retrieve the byte value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The byte value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract byte getByte(String key); + + /** + * Retrieve the byte value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract byte getByteOrDefault(String key); + + /** + * Associate a NBT byte value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, byte value); + + /** + * Retrieve the short value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The short value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract Short getShort(String key); + + /** + * Retrieve the short value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract short getShortOrDefault(String key); + + /** + * Associate a NBT short value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, short value); + + /** + * Retrieve the integer value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The integer value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract int getInteger(String key); + + /** + * Retrieve the integer value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract int getIntegerOrDefault(String key); + + /** + * Associate a NBT integer value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, int value); + + /** + * Retrieve the long value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The long value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract long getLong(String key); + + /** + * Retrieve the long value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract long getLongOrDefault(String key); + + /** + * Associate a NBT long value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, long value); + + /** + * Retrieve the float value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The float value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract float getFloat(String key); + + /** + * Retrieve the float value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract float getFloatOrDefault(String key); + + /** + * Associate a NBT float value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, float value); + + /** + * Retrieve the double value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The double value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract double getDouble(String key); + + /** + * Retrieve the double value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + public abstract double getDoubleOrDefault(String key); + + /** + * Associate a NBT double value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, double value); + + /** + * Retrieve the byte array value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The byte array value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract byte[] getByteArray(String key); + + /** + * Associate a NBT byte array value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, byte[] value); + + /** + * Retrieve the integer array value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The integer array value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract int[] getIntegerArray(String key); + + /** + * Associate a NBT integer array value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, int[] value); + + /** + * Retrieve the compound (map) value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The compound value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract NbtCompound getCompound(String key); + + /** + * Retrieve a compound (map) value by its key, or create a new compound if it doesn't exist. + * @param key - the key of the entry to find or create. + * @return The compound value that was retrieved or just created. + */ + public abstract NbtCompound getCompoundOrDefault(String key); + + /** + * Associate a NBT compound with its name as key. + * @param compound - the compound value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(WrappedCompound compound); + + /** + * Retrieve the NBT list value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The NBT list value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + public abstract NbtList getList(String key); + + /** + * Retrieve a NBT list value by its key, or create a new list if it doesn't exist. + * @param key - the key of the entry to find or create. + * @return The compound value that was retrieved or just created. + */ + public abstract NbtList getListOrDefault(String key); + + /** + * Associate a NBT list with the given key. + * @param list - the list value. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(NbtList list); + + /** + * Associate a new NBT list with the given key. + * @param key - the key and name of the new NBT list. + * @param list - the list of NBT elements. + * @return This current compound, for chaining. + */ + public abstract NbtCompound put(String key, Collection> list); + + /** + * Retrieve an iterator view of the NBT tags stored in this compound. + * @return The tags stored in this compound. + */ + public abstract Iterator> iterator(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java new file mode 100644 index 00000000..6ae15713 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java @@ -0,0 +1,363 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.bukkit.inventory.ItemStack; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.BukkitConverters; + +/** + * Factory methods for creating NBT elements, lists and compounds. + * + * @author Kristian + */ +public class NbtFactory { + // Used to create the underlying tag + private static Method methodCreateTag; + + // Item stack trickery + private static StructureModifier itemStackModifier; + + /** + * Attempt to cast this NBT tag as a compund. + * @param tag - the NBT tag to cast. + * @return This instance as a compound. + * @throws UnsupportedOperationException If this is not a compound. + */ + public static NbtCompound asCompound(NbtBase tag) { + if (tag instanceof NbtCompound) + return (NbtCompound) tag; + else if (tag != null) + throw new UnsupportedOperationException( + "Cannot cast a " + tag.getClass() + "( " + tag.getType() + ") to TAG_COMPUND."); + else + throw new IllegalArgumentException("Tag cannot be NULL."); + } + + /** + * Attempt to cast this NBT tag as a list. + * @param tag - the NBT tag to cast. + * @return This instance as a list. + * @throws UnsupportedOperationException If this is not a list. + */ + public static NbtList asList(NbtBase tag) { + if (tag instanceof NbtList) + return (NbtList) tag; + else if (tag != null) + throw new UnsupportedOperationException( + "Cannot cast a " + tag.getClass() + "( " + tag.getType() + ") to TAG_LIST."); + else + throw new IllegalArgumentException("Tag cannot be NULL."); + } + + /** + * Get a NBT wrapper from a NBT base. + *

+ * This may clone the content if the NbtBase is not a NbtWrapper. + * @param base - the base class. + * @return A NBT wrapper. + */ + @SuppressWarnings("unchecked") + public static NbtWrapper fromBase(NbtBase base) { + if (base instanceof NbtWrapper) { + return (NbtWrapper) base; + } else { + if (base.getType() == NbtType.TAG_COMPOUND) { + // Load into a NBT-backed wrapper + WrappedCompound copy = WrappedCompound.fromName(base.getName()); + T value = base.getValue(); + + copy.setValue((Map>) value); + return (NbtWrapper) copy; + + } else if (base.getType() == NbtType.TAG_LIST) { + // As above + NbtList copy = WrappedList.fromName(base.getName()); + + copy.setValue((List>) base.getValue()); + return (NbtWrapper) copy; + + } else { + // Copy directly + NbtWrapper copy = ofWrapper(base.getType(), base.getName()); + + copy.setValue(base.getValue()); + return copy; + } + } + } + + /** + * Construct a wrapper for an NBT tag stored (in memory) in an item stack. This is where + * auxillary data such as enchanting, name and lore is stored. It doesn't include the items + * material, damage value or count. + *

+ * The item stack must be a wrapper for a CraftItemStack. Use + * {@link MinecraftReflection#getBukkitItemStack(ItemStack)} if not. + * @param stack - the item stack. + * @return A wrapper for its NBT tag. + */ + public static NbtWrapper fromItemTag(ItemStack stack) { + if (!MinecraftReflection.isCraftItemStack(stack)) + throw new IllegalArgumentException("Stack must be a CraftItemStack."); + + Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack); + + if (itemStackModifier == null) { + itemStackModifier = new StructureModifier(nmsStack.getClass(), Object.class, false); + } + + // Use the first and best NBT tag + StructureModifier> modifier = itemStackModifier. + withTarget(nmsStack). + withType(MinecraftReflection.getNBTBaseClass(), BukkitConverters.getNbtConverter()); + NbtBase result = modifier.read(0); + + // Create the tag if it doesn't exist + if (result == null) { + result = NbtFactory.ofCompound("tag"); + modifier.write(0, result); + } + return fromBase(result); + } + + /** + * Initialize a NBT wrapper. + * @param handle - the underlying net.minecraft.server object to wrap. + * @return A NBT wrapper. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static NbtWrapper fromNMS(Object handle) { + WrappedElement partial = new WrappedElement(handle); + + // See if this is actually a compound tag + if (partial.getType() == NbtType.TAG_COMPOUND) + return (NbtWrapper) new WrappedCompound(handle); + else if (partial.getType() == NbtType.TAG_LIST) + return new WrappedList(handle); + else + return partial; + } + + /** + * Constructs a NBT tag of type string. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, String value) { + return ofWrapper(NbtType.TAG_STRING, name, value); + } + + /** + * Constructs a NBT tag of type byte. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, byte value) { + return ofWrapper(NbtType.TAG_BYTE, name, value); + } + + /** + * Constructs a NBT tag of type short. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, short value) { + return ofWrapper(NbtType.TAG_SHORT, name, value); + } + + /** + * Constructs a NBT tag of type int. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, int value) { + return ofWrapper(NbtType.TAG_INT, name, value); + } + + /** + * Constructs a NBT tag of type long. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, long value) { + return ofWrapper(NbtType.TAG_LONG, name, value); + } + + /** + * Constructs a NBT tag of type float. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, float value) { + return ofWrapper(NbtType.TAG_FLOAT, name, value); + } + + /** + * Constructs a NBT tag of type double. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, double value) { + return ofWrapper(NbtType.TAG_DOUBLE, name, value); + } + + /** + * Constructs a NBT tag of type byte array. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, byte[] value) { + return ofWrapper(NbtType.TAG_BYTE_ARRAY, name, value); + } + + /** + * Constructs a NBT tag of type int array. + * @param name - name of the tag. + * @param value - value of the tag. + * @return The constructed NBT tag. + */ + public static NbtBase of(String name, int[] value) { + return ofWrapper(NbtType.TAG_INT_ARRAY, name, value); + } + + /** + * Construct a new NBT compound initialized with a given list of NBT values. + * @param name - the name of the compound wrapper. + * @param list - the list of elements to add. + * @return The new wrapped NBT compound. + */ + public static NbtCompound ofCompound(String name, Collection> list) { + return WrappedCompound.fromList(name, list); + } + + /** + * Construct a new NBT compound wrapper. + * @param name - the name of the compound wrapper. + * @return The new wrapped NBT compound. + */ + public static NbtCompound ofCompound(String name) { + return WrappedCompound.fromName(name); + } + + /** + * Construct a NBT list of out an array of values. + * @param name - name of this list. + * @param elements - elements to add. + * @return The new filled NBT list. + */ + public static NbtList ofList(String name, T... elements) { + return WrappedList.fromArray(name, elements); + } + + /** + * Construct a NBT list of out a list of values. + * @param name - name of this list. + * @param elements - elements to add. + * @return The new filled NBT list. + */ + public static NbtList ofList(String name, Collection elements) { + return WrappedList.fromList(name, elements); + } + + /** + * Create a new NBT wrapper from a given type. + * @param type - the NBT type. + * @param name - the name of the NBT tag. + * @return The new wrapped NBT tag. + * @throws FieldAccessException If we're unable to create the underlying tag. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static NbtWrapper ofWrapper(NbtType type, String name) { + if (type == null) + throw new IllegalArgumentException("type cannot be NULL."); + if (type == NbtType.TAG_END) + throw new IllegalArgumentException("Cannot create a TAG_END."); + + if (methodCreateTag == null) { + Class base = MinecraftReflection.getNBTBaseClass(); + + // Use the base class + methodCreateTag = FuzzyReflection.fromClass(base). + getMethodByParameters("createTag", base, new Class[] { byte.class, String.class }); + } + + try { + Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name); + + if (type == NbtType.TAG_COMPOUND) + return (NbtWrapper) new WrappedCompound(handle); + else if (type == NbtType.TAG_LIST) + return (NbtWrapper) new WrappedList(handle); + else + return new WrappedElement(handle); + + } catch (Exception e) { + // Inform the caller + throw new FieldAccessException( + String.format("Cannot create NBT element %s (type: %s)", name, type), + e); + } + } + + /** + * Create a new NBT wrapper from a given type. + * @param type - the NBT type. + * @param name - the name of the NBT tag. + * @param value - the value of the new tag. + * @return The new wrapped NBT tag. + * @throws FieldAccessException If we're unable to create the underlying tag. + */ + public static NbtWrapper ofWrapper(NbtType type, String name, T value) { + NbtWrapper created = ofWrapper(type, name); + + // Update the value + created.setValue(value); + return created; + } + + /** + * Create a new NBT wrapper from a given type. + * @param type - type of the NBT value. + * @param name - the name of the NBT tag. + * @param value - the value of the new tag. + * @return The new wrapped NBT tag. + * @throws FieldAccessException If we're unable to create the underlying tag. + * @throws IllegalArgumentException If the given class type is not valid NBT. + */ + public static NbtWrapper ofWrapper(Class type, String name, T value) { + return ofWrapper(NbtType.getTypeFromClass(type), name, value); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java new file mode 100644 index 00000000..85ef13de --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtList.java @@ -0,0 +1,139 @@ +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a list of NBT tags of the same type without names. + *

+ * Use {@link NbtFactory} to load or create an instance. + *

+ * The {@link NbtBase#getValue()} method returns a {@link java.util.List} that will correctly return the content + * of this NBT list, but may throw an {@link UnsupportedOperationException} for any of the write operations. + * + * @author Kristian + * + * @param - the value type of each NBT tag. + */ +public interface NbtList extends NbtBase>>, Iterable { + /** + * The name of every NBT tag in a list. + */ + public static String EMPTY_NAME = ""; + + /** + * Get the type of each element. + *

+ * This will be {@link NbtType#TAG_END TAG_END} if the NBT list has just been created. + * @return Element type. + */ + public abstract NbtType getElementType(); + + /** + * Set the type of each element. + * @param type - type of each element. + */ + public abstract void setElementType(NbtType type); + + /** + * Add a value to a typed list by attempting to convert it to the nearest value. + *

+ * Note that the list must be typed by setting {@link #setElementType(NbtType)} before calling this function. + * @param value - the value to add. + */ + public abstract void addClosest(Object value); + + /** + * Add a NBT list or NBT compound to the list. + * @param element + */ + public abstract void add(NbtBase element); + + /** + * Add a new string element to the list. + * @param value - the string element to add. + * @throws IllegalArgumentException If this is not a list of strings. + */ + public abstract void add(String value); + + /** + * Add a new byte element to the list. + * @param value - the byte element to add. + * @throws IllegalArgumentException If this is not a list of bytes. + */ + public abstract void add(byte value); + + /** + * Add a new short element to the list. + * @param value - the short element to add. + * @throws IllegalArgumentException If this is not a list of shorts. + */ + public abstract void add(short value); + + /** + * Add a new integer element to the list. + * @param value - the string element to add. + * @throws IllegalArgumentException If this is not a list of integers. + */ + public abstract void add(int value); + + /** + * Add a new long element to the list. + * @param value - the string element to add. + * @throws IllegalArgumentException If this is not a list of longs. + */ + public abstract void add(long value); + + /** + * Add a new double element to the list. + * @param value - the double element to add. + * @throws IllegalArgumentException If this is not a list of doubles. + */ + public abstract void add(double value); + + /** + * Add a new byte array element to the list. + * @param value - the byte array element to add. + * @throws IllegalArgumentException If this is not a list of byte arrays. + */ + public abstract void add(byte[] value); + + /** + * Add a new int array element to the list. + * @param value - the int array element to add. + * @throws IllegalArgumentException If this is not a list of int arrays. + */ + public abstract void add(int[] value); + + /** + * Remove a given object from the list. + * @param remove - the object to remove. + */ + public abstract void remove(Object remove); + + /** + * Retrieve an element by index. + * @param index - index of the element to retrieve. + * @return The element to retrieve. + * @throws IndexOutOfBoundsException If the index is out of range (index < 0 || index >= size()) + */ + public abstract TType getValue(int index); + + /** + * Retrieve the number of elements in this list. + * @return The number of elements in this list. + */ + public abstract int size(); + + /** + * Retrieve each NBT tag in this list. + * @return A view of NBT tag in this list. + */ + public abstract Collection> asCollection(); + + /** + * Iterate over all the elements in this list. + */ + public abstract Iterator iterator(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java new file mode 100644 index 00000000..2ef6e59d --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtType.java @@ -0,0 +1,184 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.primitives.Primitives; + +/** + * Represents all the element types + * + * @author Kristian + */ +public enum NbtType { + /** + * Used to mark the end of compound tags. CANNOT be constructed. + */ + TAG_END(0, Void.class), + + /** + * A signed 1 byte integral type. Sometimes used for booleans. + */ + TAG_BYTE(1, byte.class), + + /** + * A signed 2 byte integral type. + */ + TAG_SHORT(2, short.class), + + /** + * A signed 4 byte integral type. + */ + TAG_INT(3, int.class), + + /** + * A signed 8 byte integral type. + */ + TAG_LONG(4, long.class), + + /** + * A signed 4 byte floating point type. + */ + TAG_FLOAT(5, float.class), + + /** + * A signed 8 byte floating point type. + */ + TAG_DOUBLE(6, double.class), + + /** + * An array of bytes. + */ + TAG_BYTE_ARRAY(7, byte[].class), + + /** + * An array of TAG_Int's payloads.. + */ + TAG_INT_ARRAY(11, int[].class), + + /** + * A UTF-8 string + */ + TAG_STRING(8, String.class), + + /** + * A list of tag payloads, without repeated tag IDs or any tag names. + */ + TAG_LIST(9, List.class), + + /** + * A list of fully formed tags, including their IDs, names, and payloads. No two tags may have the same name. + */ + TAG_COMPOUND(10, Map.class); + + private int rawID; + private Class valueType; + + // Used to lookup a specified NBT + private static NbtType[] lookup; + + // Lookup NBT by class + private static Map, NbtType> classLookup; + + static { + NbtType[] values = values(); + lookup = new NbtType[values.length]; + classLookup = new HashMap, NbtType>(); + + // Initialize lookup tables + for (NbtType type : values) { + lookup[type.getRawID()] = type; + classLookup.put(type.getValueType(), type); + + // Add a wrapper type + if (type.getValueType().isPrimitive()) { + classLookup.put(Primitives.wrap(type.getValueType()), type); + } + } + + // Additional lookup + classLookup.put(NbtList.class, TAG_LIST); + classLookup.put(NbtCompound.class, TAG_COMPOUND); + } + + private NbtType(int rawID, Class valueType) { + this.rawID = rawID; + this.valueType = valueType; + } + + /** + * Determine if the given NBT can store multiple children NBT tags. + * @return TRUE if this is a composite NBT tag, FALSE otherwise. + */ + public boolean isComposite() { + return this == TAG_COMPOUND || this == TAG_LIST; + } + + /** + * Retrieves the raw unique integer that identifies the type of the parent NBT element. + * @return Integer that uniquely identifying the type. + */ + public int getRawID() { + return rawID; + } + + /** + * Retrieves the type of the value stored in the NBT element. + * @return Type of the stored value. + */ + public Class getValueType() { + return valueType; + } + + /** + * Retrieve an NBT type from a given raw ID. + * @param rawID - the raw ID to lookup. + * @return The associated NBT value. + */ + public static NbtType getTypeFromID(int rawID) { + if (rawID < 0 || rawID >= lookup.length) + throw new IllegalArgumentException("Unrecognized raw ID " + rawID); + return lookup[rawID]; + } + + /** + * Retrieve an NBT type from the given Java class. + * @param clazz - type of the value the NBT type can contain. + * @return The NBT type. + * @throws IllegalArgumentException If this class type cannot be represented by NBT tags. + */ + public static NbtType getTypeFromClass(Class clazz) { + NbtType result = classLookup.get(clazz); + + // Try to lookup this value + if (result != null) { + return result; + } else { + // Look for interfaces + for (Class implemented : clazz.getInterfaces()) { + if (classLookup.containsKey(implemented)) + return classLookup.get(implemented); + } + + throw new IllegalArgumentException("No NBT tag can represent a " + clazz); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java new file mode 100644 index 00000000..8a9a3ec9 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtVisitor.java @@ -0,0 +1,43 @@ +package com.comphenix.protocol.wrappers.nbt; + +/** + * A visitor that can enumerate a NBT tree structure. + * + * @author Kristian + */ +public interface NbtVisitor { + /** + * Visit a leaf node, which is a NBT tag with a primitive or String value. + * @param node - the visited leaf node. + * @return TRUE to continue visiting children at this level, FALSE otherwise. + */ + public boolean visit(NbtBase node); + + /** + * Begin visiting a list node that contains multiple child nodes of the same type. + * @param list - the NBT tag to process. + * @return TRUE to visit the child nodes of this list, FALSE otherwise. + */ + public boolean visitEnter(NbtList list); + + /** + * Begin visiting a compound node that contains multiple child nodes of different types. + * @param compound - the NBT tag to process. + * @return TRUE to visit the child nodes of this compound, FALSE otherwise. + */ + public boolean visitEnter(NbtCompound compound); + + /** + * Stop visiting a list node. + * @param list - the list we're done visiting. + * @return TRUE for the parent to visit any subsequent sibling nodes, FALSE otherwise. + */ + public boolean visitLeave(NbtList list); + + /** + * Stop visiting a compound node. + * @param compound - the compound we're done visting. + * @return TRUE for the parent to visit any subsequent sibling nodes, FALSE otherwise + */ + public boolean visitLeave(NbtCompound compound); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java new file mode 100644 index 00000000..da1916f0 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtWrapper.java @@ -0,0 +1,43 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.io.DataOutput; + +/** + * Indicates that this NBT wraps an underlying net.minecraft.server instance. + *

+ * Use {@link NbtFactory} to load or create instances. + * + * @author Kristian + * + * @param - type of the value that is stored. + */ +public interface NbtWrapper extends NbtBase { + /** + * Retrieve the underlying net.minecraft.server instance. + * @return The NMS instance. + */ + public Object getHandle(); + + /** + * Write the current NBT tag to an output stream. + * @param destination - the destination stream. + */ + public void write(DataOutput destination); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java new file mode 100644 index 00000000..410e093d --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java @@ -0,0 +1,630 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.io.DataOutput; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; + +/** + * A concrete implementation of an NbtCompound that wraps an underlying NMS Compound. + * + * @author Kristian + */ +class WrappedCompound implements NbtWrapper>>, Iterable>, NbtCompound { + // A list container + private WrappedElement> container; + + // Saved wrapper map + private ConvertedMap> savedMap; + + /** + * Construct a new NBT compound wrapper. + * @param name - the name of the wrapper. + * @return The wrapped NBT compound. + */ + public static WrappedCompound fromName(String name) { + // Simplify things for the caller + return (WrappedCompound) NbtFactory.>>ofWrapper(NbtType.TAG_COMPOUND, name); + } + + /** + * Construct a new NBT compound wrapper initialized with a given list of NBT values. + * @param name - the name of the compound wrapper. + * @param list - the list of elements to add. + * @return The new wrapped NBT compound. + */ + public static NbtCompound fromList(String name, Collection> list) { + WrappedCompound copy = fromName(name); + + for (NbtBase base : list) + copy.getValue().put(base.getName(), base); + return copy; + } + + /** + * Construct a wrapped compound from a given NMS handle. + * @param handle - the NMS handle. + */ + public WrappedCompound(Object handle) { + this.container = new WrappedElement>(handle); + } + + @Override + public boolean accept(NbtVisitor visitor) { + // Enter this node? + if (visitor.visitEnter(this)) { + for (NbtBase node : this) { + if (!node.accept(visitor)) + break; + } + } + + return visitor.visitLeave(this); + } + + @Override + public Object getHandle() { + return container.getHandle(); + } + + @Override + public NbtType getType() { + return NbtType.TAG_COMPOUND; + } + + @Override + public String getName() { + return container.getName(); + } + + @Override + public void setName(String name) { + container.setName(name); + } + + /** + * Determine if an entry with the given key exists or not. + * @param key - the key to lookup. + * @return TRUE if an entry with the given key exists, FALSE otherwise. + */ + @Override + public boolean containsKey(String key) { + return getValue().containsKey(key); + } + + /** + * Retrieve a Set view of the keys of each entry in this compound. + * @return The keys of each entry. + */ + @Override + public Set getKeys() { + return getValue().keySet(); + } + + @Override + public Map> getValue() { + // Return a wrapper map + if (savedMap == null) { + savedMap = new ConvertedMap>(container.getValue()) { + @Override + protected Object toInner(NbtBase outer) { + if (outer == null) + return null; + return NbtFactory.fromBase(outer).getHandle(); + } + + protected NbtBase toOuter(Object inner) { + if (inner == null) + return null; + return NbtFactory.fromNMS(inner); + }; + + @Override + public String toString() { + return WrappedCompound.this.toString(); + } + }; + } + return savedMap; + } + + @Override + public void setValue(Map> newValue) { + // Write all the entries + for (Map.Entry> entry : newValue.entrySet()) { + put(entry.getValue()); + } + } + + /** + * Retrieve the value of a given entry. + * @param key - key of the entry to retrieve. + * @return The value of this entry, or NULL if not found. + */ + @Override + @SuppressWarnings("unchecked") + public NbtBase getValue(String key) { + return (NbtBase) getValue().get(key); + } + + /** + * Retrieve a value by its key, or assign and return a new NBT element if it doesn't exist. + * @param key - the key of the entry to find or create. + * @param type - the NBT element we will create if not found. + * @return The value that was retrieved or just created. + */ + @Override + public NbtBase getValueOrDefault(String key, NbtType type) { + NbtBase nbt = getValue(key); + + // Create or get a compound + if (nbt == null) + put(nbt = NbtFactory.ofWrapper(type, key)); + else if (nbt.getType() != type) + throw new IllegalArgumentException("Cannot get tag " + nbt + ": Not a " + type); + + return nbt; + } + + /** + * Retrieve a value, or throw an exception. + * @param key - the key to retrieve. + * @return The value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + private NbtBase getValueExact(String key) { + NbtBase value = getValue(key); + + // Only return a legal key + if (value != null) + return value; + else + throw new IllegalArgumentException("Cannot find key " + key); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public NbtBase>> deepClone() { + return (NbtBase) container.deepClone(); + } + + /** + * Set a entry based on its name. + * @param entry - entry with a name and value. + * @return This compound, for chaining. + */ + @Override + public NbtCompound put(NbtBase entry) { + getValue().put(entry.getName(), entry); + return this; + } + + /** + * Retrieve the string value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The string value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public String getString(String key) { + return (String) getValueExact(key).getValue(); + } + + /** + * Retrieve the string value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public String getStringOrDefault(String key) { + return (String) getValueOrDefault(key, NbtType.TAG_STRING).getValue(); + } + + /** + * Associate a NBT string value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, String value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the byte value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The byte value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public byte getByte(String key) { + return (Byte) getValueExact(key).getValue(); + } + + /** + * Retrieve the byte value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public byte getByteOrDefault(String key) { + return (Byte) getValueOrDefault(key, NbtType.TAG_BYTE).getValue(); + } + + /** + * Associate a NBT byte value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, byte value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the short value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The short value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public Short getShort(String key) { + return (Short) getValueExact(key).getValue(); + } + + /** + * Retrieve the short value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public short getShortOrDefault(String key) { + return (Short) getValueOrDefault(key, NbtType.TAG_SHORT).getValue(); + } + + /** + * Associate a NBT short value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, short value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the integer value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The integer value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public int getInteger(String key) { + return (Integer) getValueExact(key).getValue(); + } + + /** + * Retrieve the integer value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public int getIntegerOrDefault(String key) { + return (Integer) getValueOrDefault(key, NbtType.TAG_INT).getValue(); + } + + /** + * Associate a NBT integer value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, int value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the long value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The long value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public long getLong(String key) { + return (Long) getValueExact(key).getValue(); + } + + /** + * Retrieve the long value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public long getLongOrDefault(String key) { + return (Long) getValueOrDefault(key, NbtType.TAG_LONG).getValue(); + } + + /** + * Associate a NBT long value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, long value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the float value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The float value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public float getFloat(String key) { + return (Float) getValueExact(key).getValue(); + } + + /** + * Retrieve the float value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public float getFloatOrDefault(String key) { + return (Float) getValueOrDefault(key, NbtType.TAG_FLOAT).getValue(); + } + + /** + * Associate a NBT float value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, float value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the double value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The double value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public double getDouble(String key) { + return (Double) getValueExact(key).getValue(); + } + + /** + * Retrieve the double value of an existing entry, or from a new default entry if it doesn't exist. + * @param key - the key of the entry. + * @return The value that was retrieved or just created. + */ + @Override + public double getDoubleOrDefault(String key) { + return (Double) getValueOrDefault(key, NbtType.TAG_DOUBLE).getValue(); + } + + /** + * Associate a NBT double value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, double value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the byte array value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The byte array value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public byte[] getByteArray(String key) { + return (byte[]) getValueExact(key).getValue(); + } + + /** + * Associate a NBT byte array value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, byte[] value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the integer array value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The integer array value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + public int[] getIntegerArray(String key) { + return (int[]) getValueExact(key).getValue(); + } + + /** + * Associate a NBT integer array value with the given key. + * @param key - the key and NBT name. + * @param value - the value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, int[] value) { + getValue().put(key, NbtFactory.of(key, value)); + return this; + } + + /** + * Retrieve the compound (map) value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The compound value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + @SuppressWarnings("rawtypes") + public NbtCompound getCompound(String key) { + return (NbtCompound) ((NbtBase) getValueExact(key)); + } + + /** + * Retrieve a compound (map) value by its key, or create a new compound if it doesn't exist. + * @param key - the key of the entry to find or create. + * @return The compound value that was retrieved or just created. + */ + @Override + public NbtCompound getCompoundOrDefault(String key) { + return (NbtCompound) getValueOrDefault(key, NbtType.TAG_COMPOUND); + } + + /** + * Associate a NBT compound with its name as key. + * @param compound - the compound value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(WrappedCompound compound) { + getValue().put(compound.getName(), compound); + return this; + } + + /** + * Retrieve the NBT list value of an entry identified by a given key. + * @param key - the key of the entry. + * @return The NBT list value of the entry. + * @throws IllegalArgumentException If the key doesn't exist. + */ + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public NbtList getList(String key) { + return (NbtList) getValueExact(key); + } + + /** + * Retrieve a NBT list value by its key, or create a new list if it doesn't exist. + * @param key - the key of the entry to find or create. + * @return The compound value that was retrieved or just created. + */ + @Override + @SuppressWarnings("unchecked") + public NbtList getListOrDefault(String key) { + return (NbtList) getValueOrDefault(key, NbtType.TAG_LIST); + } + + /** + * Associate a NBT list with the given key. + * @param list - the list value. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(NbtList list) { + getValue().put(list.getName(), list); + return this; + } + + @Override + public NbtCompound put(String key, NbtBase entry) { + // Don't modify the original NBT + NbtBase clone = entry.deepClone(); + + clone.setName(key); + return put(clone); + } + + /** + * Associate a new NBT list with the given key. + * @param key - the key and name of the new NBT list. + * @param list - the list of NBT elements. + * @return This current compound, for chaining. + */ + @Override + public NbtCompound put(String key, Collection> list) { + return put(WrappedList.fromList(key, list)); + } + + @Override + public void write(DataOutput destination) { + NbtBinarySerializer.DEFAULT.serialize(container, destination); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WrappedCompound) { + WrappedCompound other = (WrappedCompound) obj; + return container.equals(other.container); + } + return false; + } + + @Override + public int hashCode() { + return container.hashCode(); + } + + @Override + public Iterator> iterator() { + return getValue().values().iterator(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + builder.append("{"); + builder.append("\"name\": \"" + getName() + "\""); + + for (NbtBase element : this) { + builder.append(", "); + + // Wrap in quotation marks + if (element.getType() == NbtType.TAG_STRING) + builder.append("\"" + element.getName() + "\": \"" + element.getValue() + "\""); + else + builder.append("\"" + element.getName() + "\": " + element.getValue()); + } + + builder.append("}"); + return builder.toString(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java new file mode 100644 index 00000000..936f4c2b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java @@ -0,0 +1,243 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.io.DataOutput; +import java.lang.reflect.Method; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; +import com.google.common.base.Objects; + +/** + * Represents a wrapped NBT tag element, composite or not. + * + * @author Kristian + * @param - type of the value field. + */ +class WrappedElement implements NbtWrapper { + // Structure modifier for the base class + private static volatile StructureModifier baseModifier; + + // For retrieving the current type ID + private static volatile Method methodGetTypeID; + + // For handling cloning + private static volatile Method methodClone; + + // Structure modifiers for the different NBT elements + private static StructureModifier[] modifiers = new StructureModifier[NbtType.values().length]; + + // The underlying NBT object + private Object handle; + + // Saved type + private NbtType type; + + /** + * Initialize a NBT wrapper for a generic element. + * @param handle - the NBT element to wrap. + */ + public WrappedElement(Object handle) { + this.handle = handle; + } + + /** + * Retrieve the modifier (with no target) that is used to read and write the NBT name. + * @return A modifier for accessing the NBT name. + */ + protected static StructureModifier getBaseModifier() { + if (baseModifier == null) { + Class base = MinecraftReflection.getNBTBaseClass(); + + // This will be the same for all classes, so we'll share modifier + baseModifier = new StructureModifier(base, Object.class, false).withType(String.class); + } + + return baseModifier.withType(String.class); + } + + /** + * Retrieve a modifier (with no target) that is used to read and write the NBT value. + * @return The value modifier. + */ + protected StructureModifier getCurrentModifier() { + NbtType type = getType(); + + return getCurrentBaseModifier().withType(type.getValueType()); + } + + /** + * Get the object modifier (with no target) for the current underlying NBT object. + * @return The generic modifier. + */ + @SuppressWarnings("unchecked") + protected StructureModifier getCurrentBaseModifier() { + int index = getType().ordinal(); + StructureModifier modifier = (StructureModifier) modifiers[index]; + + // Double checked locking + if (modifier == null) { + synchronized (this) { + if (modifiers[index] == null) { + modifiers[index] = new StructureModifier(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false); + } + modifier = (StructureModifier) modifiers[index]; + } + } + + return modifier; + } + + @Override + public boolean accept(NbtVisitor visitor) { + return visitor.visit(this); + } + + /** + * Retrieve the underlying NBT tag object. + * @return The underlying Minecraft tag object. + */ + @Override + public Object getHandle() { + return handle; + } + + @Override + public NbtType getType() { + if (methodGetTypeID == null) { + // Use the base class + methodGetTypeID = FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass()). + getMethodByParameters("getTypeID", byte.class, new Class[0]); + } + if (type == null) { + try { + type = NbtType.getTypeFromID((Byte) methodGetTypeID.invoke(handle)); + } catch (Exception e) { + throw new FieldAccessException("Cannot get NBT type of " + handle, e); + } + } + + return type; + } + + /** + * Retrieve the sub element type of the underlying NMS NBT list. + * @return The NBT sub type. + */ + public NbtType getSubType() { + int subID = getCurrentBaseModifier().withType(byte.class).withTarget(handle).read(0); + return NbtType.getTypeFromID(subID); + } + + /** + * Set the sub element type of the underlying NMS NBT list. + * @param type - the new sub element type. + */ + public void setSubType(NbtType type) { + byte subID = (byte) type.getRawID(); + getCurrentBaseModifier().withType(byte.class).withTarget(handle).write(0, subID); + } + + @Override + public String getName() { + return getBaseModifier().withTarget(handle).read(0); + } + + @Override + public void setName(String name) { + getBaseModifier().withTarget(handle).write(0, name); + } + + @Override + public TType getValue() { + return getCurrentModifier().withTarget(handle).read(0); + } + + @Override + public void setValue(TType newValue) { + getCurrentModifier().withTarget(handle).write(0, newValue); + } + + @Override + public void write(DataOutput destination) { + // No need to cache this object + NbtBinarySerializer.DEFAULT.serialize(this, destination); + } + + @Override + public NbtBase deepClone() { + if (methodClone == null) { + Class base = MinecraftReflection.getNBTBaseClass(); + + // Use the base class + methodClone = FuzzyReflection.fromClass(base). + getMethodByParameters("clone", base, new Class[0]); + } + + try { + return NbtFactory.fromNMS(methodClone.invoke(handle)); + } catch (Exception e) { + throw new FieldAccessException("Unable to clone " + handle, e); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(getName(), getType(), getValue()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof NbtBase) { + NbtBase other = (NbtBase) obj; + + // Make sure we're dealing with the same type + if (other.getType().equals(getType())) { + return Objects.equal(getName(), other.getName()) && + Objects.equal(getValue(), other.getValue()); + } + } + return false; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + String name = getName(); + + result.append("{"); + + if (name != null && name.length() > 0) + result.append("name: '" + name + "', "); + + result.append("value: "); + + // Wrap quotation marks + if (getType() == NbtType.TAG_STRING) + result.append("'" + getValue() + "'"); + else + result.append(getValue()); + + result.append("}"); + return result.toString(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java new file mode 100644 index 00000000..7e281b04 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java @@ -0,0 +1,407 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import java.io.DataOutput; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nullable; + +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; + +/** + * Represents a concrete implementation of an NBT list that wraps an underlying NMS list. + * @author Kristian + * + * @param - the type of the value in each NBT sub element. + */ +class WrappedList implements NbtWrapper>>, Iterable, NbtList { + // A list container + private WrappedElement> container; + + // Saved wrapper list + private ConvertedList> savedList; + + // Element type + private NbtType elementType = NbtType.TAG_END; + + /** + * Construct a new empty NBT list. + * @param name - name of this list. + * @return The new empty NBT list. + */ + @SuppressWarnings("unchecked") + public static NbtList fromName(String name) { + return (NbtList) NbtFactory.>>ofWrapper(NbtType.TAG_LIST, name); + } + + /** + * Construct a NBT list of out an array of values.. + * @param name - name of this list. + * @param elements - values to add. + * @return The new filled NBT list. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static NbtList fromArray(String name, T... elements) { + NbtList result = fromName(name); + + for (T element : elements) { + if (element == null) + throw new IllegalArgumentException("An NBT list cannot contain a null element!"); + + if (element instanceof NbtBase) + result.add((NbtBase) element); + else + result.add(NbtFactory.ofWrapper(element.getClass(), EMPTY_NAME, element)); + } + return result; + } + + /** + * Construct a NBT list of out a list of NBT elements. + * @param name - name of this list. + * @param elements - elements to add. + * @return The new filled NBT list. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static NbtList fromList(String name, Collection elements) { + NbtList result = fromName(name); + + for (T element : elements) { + if (element == null) + throw new IllegalArgumentException("An NBT list cannot contain a null element!"); + + if (element instanceof NbtBase) + result.add((NbtBase) element); + else + result.add(NbtFactory.ofWrapper(element.getClass(), EMPTY_NAME, element)); + } + return result; + } + + /** + * Construct a list from an NMS instance. + * @param handle - NMS instance. + */ + public WrappedList(Object handle) { + this.container = new WrappedElement>(handle); + this.elementType = container.getSubType(); + } + + @Override + public boolean accept(NbtVisitor visitor) { + // Enter this node? + if (visitor.visitEnter(this)) { + for (NbtBase node : getValue()) { + if (!node.accept(visitor)) + break; + } + } + + return visitor.visitLeave(this); + } + + @Override + public Object getHandle() { + return container.getHandle(); + } + + @Override + public NbtType getType() { + return NbtType.TAG_LIST; + } + + @Override + public NbtType getElementType() { + return elementType; + } + + @Override + public void setElementType(NbtType type) { + this.elementType = type; + container.setSubType(type); + } + + @Override + public String getName() { + return container.getName(); + } + + @Override + public void setName(String name) { + container.setName(name); + } + + @Override + public List> getValue() { + if (savedList == null) { + savedList = new ConvertedList>(container.getValue()) { + // Check and see if the element is valid + private void verifyElement(NbtBase element) { + if (element == null) + throw new IllegalArgumentException("Cannot store NULL elements in list."); + if (!element.getName().equals(EMPTY_NAME)) + throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list."); + + // Check element type + if (getElementType() != NbtType.TAG_END) { + if (!element.getType().equals(getElementType())) { + throw new IllegalArgumentException( + "Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType()); + } + } else { + container.setSubType(element.getType()); + } + } + + @Override + public boolean add(NbtBase e) { + verifyElement(e); + return super.add(e); + } + + @Override + public void add(int index, NbtBase element) { + verifyElement(element); + super.add(index, element); + } + + @Override + public boolean addAll(Collection> c) { + boolean result = false; + + for (NbtBase element : c) { + add(element); + result = true; + } + return result; + } + + @Override + protected Object toInner(NbtBase outer) { + if (outer == null) + return null; + return NbtFactory.fromBase(outer).getHandle(); + } + + @Override + protected NbtBase toOuter(Object inner) { + if (inner == null) + return null; + return NbtFactory.fromNMS(inner); + } + + @Override + public String toString() { + return WrappedList.this.toString(); + } + }; + } + return savedList; + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public NbtBase>> deepClone() { + return (NbtBase) container.deepClone(); + } + + @Override + @SuppressWarnings("unchecked") + public void addClosest(Object value) { + if (getElementType() == NbtType.TAG_END) + throw new IllegalStateException("This list has not been typed yet."); + + if (value instanceof Number) { + Number number = (Number) value; + + // Convert the number + switch (getElementType()) { + case TAG_BYTE: add(number.byteValue()); break; + case TAG_SHORT: add(number.shortValue()); break; + case TAG_INT: add(number.intValue()); break; + case TAG_LONG: add(number.longValue()); break; + case TAG_FLOAT: add(number.floatValue()); break; + case TAG_DOUBLE: add(number.doubleValue()); break; + case TAG_STRING: add(number.toString()); break; + default: + throw new IllegalArgumentException("Cannot convert " + value + " to " + getType()); + } + + } else if (value instanceof NbtBase) { + // Add the element itself + add((NbtBase) value); + + } else { + // Just add it + add((NbtBase) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value)); + } + } + + @Override + public void add(NbtBase element) { + getValue().add(element); + } + + @Override + @SuppressWarnings("unchecked") + public void add(String value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(byte value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(short value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(int value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(long value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(double value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(byte[] value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + @SuppressWarnings("unchecked") + public void add(int[] value) { + add((NbtBase) NbtFactory.of(EMPTY_NAME, value)); + } + + @Override + public int size() { + return getValue().size(); + } + + @Override + public TType getValue(int index) { + return getValue().get(index).getValue(); + } + + /** + * Retrieve each NBT tag in this list. + * @return A view of NBT tag in this list. + */ + @Override + public Collection> asCollection() { + return getValue(); + } + + @Override + public void setValue(List> newValue) { + NbtBase lastElement = null; + List list = container.getValue(); + list.clear(); + + // Set each underlying element + for (NbtBase type : newValue) { + if (type != null) { + lastElement = type; + list.add(NbtFactory.fromBase(type).getHandle()); + } else { + list.add(null); + } + } + + // Update the sub type as well + if (lastElement != null) { + container.setSubType(lastElement.getType()); + } + } + + @Override + public void write(DataOutput destination) { + NbtBinarySerializer.DEFAULT.serialize(container, destination); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WrappedList) { + @SuppressWarnings("unchecked") + WrappedList other = (WrappedList) obj; + return container.equals(other.container); + } + return false; + } + + @Override + public int hashCode() { + return container.hashCode(); + } + + @Override + public Iterator iterator() { + return Iterables.transform(getValue(), new Function, TType>() { + @Override + public TType apply(@Nullable NbtBase param) { + return param.getValue(); + } + }).iterator(); + } + + @Override + public String toString() { + // Essentially JSON + StringBuilder builder = new StringBuilder(); + + builder.append("{\"name\": \"" + getName() + "\", \"value\": ["); + + if (size() > 0) { + if (getElementType() == NbtType.TAG_STRING) + builder.append("\"" + Joiner.on("\", \"").join(this) + "\""); + else + builder.append(Joiner.on(", ").join(this)); + } + + builder.append("]}"); + return builder.toString(); + } + + @Override + public void remove(Object remove) { + getValue().remove(remove); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java new file mode 100644 index 00000000..33812c6a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -0,0 +1,88 @@ +package com.comphenix.protocol.wrappers.nbt.io; + +import java.io.DataInput; +import java.io.DataOutput; +import java.lang.reflect.Method; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.comphenix.protocol.wrappers.nbt.NbtList; +import com.comphenix.protocol.wrappers.nbt.NbtWrapper; + +public class NbtBinarySerializer { + // Used to read and write NBT + private static Method methodWrite; + private static Method methodLoad; + + /** + * Retrieve a default instance of the NBT binary serializer. + */ + public static final NbtBinarySerializer DEFAULT = new NbtBinarySerializer(); + + /** + * Write the content of a wrapped NBT tag to a stream. + * @param value - the NBT tag to write. + * @param destination - the destination stream. + */ + public void serialize(NbtBase value, DataOutput destination) { + if (methodWrite == null) { + Class base = MinecraftReflection.getNBTBaseClass(); + + // Use the base class + methodWrite = FuzzyReflection.fromClass(base). + getMethodByParameters("writeNBT", base, DataOutput.class); + } + + try { + methodWrite.invoke(null, NbtFactory.fromBase(value).getHandle(), destination); + } catch (Exception e) { + throw new FieldAccessException("Unable to write NBT " + value, e); + } + } + + /** + * Load an NBT tag from a stream. + * @param source - the input stream. + * @return An NBT tag. + */ + public NbtWrapper deserialize(DataInput source) { + if (methodLoad == null) { + Class base = MinecraftReflection.getNBTBaseClass(); + + // Use the base class + methodLoad = FuzzyReflection.fromClass(base). + getMethodByParameters("load", base, new Class[] { DataInput.class }); + } + + try { + return NbtFactory.fromNMS(methodLoad.invoke(null, source)); + } catch (Exception e) { + throw new FieldAccessException("Unable to read NBT from " + source, e); + } + } + + /** + * Load an NBT compound from a stream. + * @param source - the input stream. + * @return An NBT compound. + */ + @SuppressWarnings("rawtypes") + public NbtCompound deserializeCompound(DataInput source) { + // I always seem to override generics ... + return (NbtCompound) (NbtBase) deserialize(source); + } + + /** + * Load an NBT list from a stream. + * @param source - the input stream. + * @return An NBT list. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public NbtList deserializeList(DataInput source) { + return (NbtList) (NbtBase) deserialize(source); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializer.java new file mode 100644 index 00000000..7ddc9f1c --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializer.java @@ -0,0 +1,347 @@ +package com.comphenix.protocol.wrappers.nbt.io; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.comphenix.protocol.wrappers.nbt.NbtList; +import com.comphenix.protocol.wrappers.nbt.NbtType; +import com.comphenix.protocol.wrappers.nbt.NbtVisitor; +import com.comphenix.protocol.wrappers.nbt.NbtWrapper; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.primitives.Ints; + +/** + * Serialize and deserialize NBT information from a configuration section. + *

+ * Note that data types may be internally preserved by modifying the serialized name. This may + * be visible to the end-user. + * + * @author Kristian + */ +public class NbtConfigurationSerializer { + /** + * The default delimiter that is used to store the data type in YAML. + */ + public static final String TYPE_DELIMITER = "$"; + + /** + * A standard YAML serializer. + */ + public static final NbtConfigurationSerializer DEFAULT = new NbtConfigurationSerializer(); + + private String dataTypeDelimiter; + + /** + * Construct a serializer using {@link #TYPE_DELIMITER} as the default delimiter. + */ + public NbtConfigurationSerializer() { + this.dataTypeDelimiter = TYPE_DELIMITER; + } + + /** + * Construct a serializer using the given value as a delimiter. + * @param dataTypeDelimiter - the local data type delimiter. + */ + public NbtConfigurationSerializer(String dataTypeDelimiter) { + this.dataTypeDelimiter = dataTypeDelimiter; + } + + /** + * Retrieve the current data type delimiter. + * @return The current data type delimiter. + */ + public String getDataTypeDelimiter() { + return dataTypeDelimiter; + } + + /** + * Write the content of a NBT tag to a configuration section. + * @param value - the NBT tag to write. + * @param destination - the destination section. + */ + public void serialize(NbtBase value, final ConfigurationSection destination) { + value.accept(new NbtVisitor() { + private ConfigurationSection current = destination; + + // The current list we're working on + private List currentList; + + // Store the index of a configuration section that works like a list + private Map workingIndex = Maps.newHashMap(); + + @Override + public boolean visitEnter(NbtCompound compound) { + current = current.createSection(compound.getName()); + return true; + } + + @Override + public boolean visitEnter(NbtList list) { + Integer listIndex = getNextIndex(); + String name = getEncodedName(list, listIndex); + + if (list.getElementType().isComposite()) { + // Use a configuration section to store this list + current = current.createSection(name); + workingIndex.put(current, 0); + } else { + currentList = Lists.newArrayList(); + current.set(name, currentList); + } + return true; + } + + @Override + public boolean visitLeave(NbtCompound compound) { + current = current.getParent(); + return true; + } + + @Override + public boolean visitLeave(NbtList list) { + // Write the list to the configuration section + if (currentList != null) { + // Save and reset the temporary list + currentList = null; + } else { + // Go up a level + workingIndex.remove(current); + current = current.getParent(); + } + return true; + } + + @Override + public boolean visit(NbtBase node) { + // Are we working on a list? + if (currentList == null) { + Integer listIndex = getNextIndex(); + String name = getEncodedName(node, listIndex); + + // Save member + current.set(name, fromNodeValue(node)); + + } else { + currentList.add(fromNodeValue(node)); + } + return true; + } + + private Integer getNextIndex() { + Integer listIndex = workingIndex.get(current); + + if (listIndex != null) + return workingIndex.put(current, listIndex + 1); + else + return null; + } + + // We need to store the data type somehow + private String getEncodedName(NbtBase node, Integer index) { + if (index != null) + return index + dataTypeDelimiter + node.getType().getRawID(); + else + return node.getName() + dataTypeDelimiter + node.getType().getRawID(); + } + + private String getEncodedName(NbtList node, Integer index) { + if (index != null) + return index + dataTypeDelimiter + node.getElementType().getRawID(); + else + return node.getName() + dataTypeDelimiter + node.getElementType().getRawID(); + } + }); + } + + /** + * Read a NBT tag from a root configuration. + * @param root - configuration that contains the NBT tag. + * @param nodeName - name of the NBT tag. + * @return The read NBT tag. + */ + @SuppressWarnings("unchecked") + public NbtWrapper deserialize(ConfigurationSection root, String nodeName) { + return (NbtWrapper) readNode(root, nodeName); + } + + /** + * Read a NBT compound from a root configuration. + * @param root - configuration that contains the NBT compound. + * @param nodeName - name of the NBT compound. + * @return The read NBT compound. + */ + public NbtCompound deserializeCompound(YamlConfiguration root, String nodeName) { + return (NbtCompound) readNode(root, nodeName); + } + + /** + * Read a NBT compound from a root configuration. + * @param root - configuration that contains the NBT compound. + * @param nodeName - name of the NBT compound. + * @return The read NBT compound. + */ + @SuppressWarnings("unchecked") + public NbtList deserializeList(YamlConfiguration root, String nodeName) { + return (NbtList) readNode(root, nodeName); + } + + @SuppressWarnings("unchecked") + private NbtWrapper readNode(ConfigurationSection parent, String name) { + String[] decoded = getDecodedName(name); + Object node = parent.get(name); + NbtType type = NbtType.TAG_END; + + // It's possible that the caller isn't aware of the encoded name itself + if (node == null) { + for (String key : parent.getKeys(false)) { + decoded = getDecodedName(key); + + // Great + if (decoded[0].equals(name)) { + node = parent.get(decoded[0]); + break; + } + } + + // Inform the caller of the problem + if (node == null) { + throw new IllegalArgumentException("Unable to find node " + name + " in " + parent); + } + } + + // Attempt to decode a NBT type + if (decoded.length > 1) { + type = NbtType.getTypeFromID(Integer.parseInt(decoded[1])); + } + + // Is this a compound? + if (node instanceof ConfigurationSection) { + // Is this a list of a map? + if (type != NbtType.TAG_END) { + NbtList list = NbtFactory.ofList(decoded[0]); + ConfigurationSection section = (ConfigurationSection) node; + List sorted = sortSet(section.getKeys(false)); + + // Read everything in order + for (String key : sorted) { + NbtBase base = (NbtBase) readNode(section, key.toString()); + base.setName(NbtList.EMPTY_NAME); + list.getValue().add(base); + } + return (NbtWrapper) list; + + } else { + NbtCompound compound = NbtFactory.ofCompound(decoded[0]); + ConfigurationSection section = (ConfigurationSection) node; + + // As above + for (String key : section.getKeys(false)) + compound.put(readNode(section, key)); + return (NbtWrapper) compound; + } + + } else { + // We need to know + if (type == NbtType.TAG_END) { + throw new IllegalArgumentException("Cannot find encoded type of " + decoded[0] + " in " + name); + } + + if (node instanceof List) { + NbtList list = NbtFactory.ofList(decoded[0]); + list.setElementType(type); + + for (Object value : (List) node) { + list.addClosest(toNodeValue(value, type)); + } + + // Add the list + return (NbtWrapper) list; + + } else { + // Normal node + return NbtFactory.ofWrapper(type, decoded[0], toNodeValue(node, type)); + } + } + } + + private List sortSet(Set unsorted) { + // Convert to integers + List sorted = new ArrayList(unsorted); + + Collections.sort(sorted, new Comparator() { + @Override + public int compare(String o1, String o2) { + // Parse the name + int index1 = Integer.parseInt(getDecodedName(o1)[0]); + int index2 = Integer.parseInt(getDecodedName(o2)[0]); + return Ints.compare(index1, index2); + } + }); + return sorted; + } + + // Ensure that int arrays are converted to byte arrays + private Object fromNodeValue(NbtBase base) { + if (base.getType() == NbtType.TAG_INT_ARRAY) + return toByteArray((int[]) base.getValue()); + else + return base.getValue(); + } + + // Convert them back + public Object toNodeValue(Object value, NbtType type) { + if (type == NbtType.TAG_INT_ARRAY) + return toIntegerArray((byte[]) value); + else + return value; + } + + /** + * Convert an integer array to an equivalent byte array. + * @param data - the integer array with the data. + * @return An equivalent byte array. + */ + private static byte[] toByteArray(int[] data) { + ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4); + IntBuffer intBuffer = byteBuffer.asIntBuffer(); + + intBuffer.put(data); + return byteBuffer.array(); + } + + /** + * Convert a byte array to the equivalent integer array. + *

+ * Note that the number of byte elements are only perserved if the byte size is a multiple of four. + * @param data - the byte array to convert. + * @return The equivalent integer array. + */ + private static int[] toIntegerArray(byte[] data) { + IntBuffer source = ByteBuffer.wrap(data).asIntBuffer(); + IntBuffer copy = IntBuffer.allocate(source.capacity()); + + copy.put(source); + return copy.array(); + } + + private static String[] getDecodedName(String nodeName) { + int delimiter = nodeName.lastIndexOf('$'); + + if (delimiter > 0) + return new String[] { nodeName.substring(0, delimiter), nodeName.substring(delimiter + 1) }; + else + return new String[] { nodeName }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtTextSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtTextSerializer.java new file mode 100644 index 00000000..c9e2b8ed --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtTextSerializer.java @@ -0,0 +1,98 @@ +package com.comphenix.protocol.wrappers.nbt.io; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtList; +import com.comphenix.protocol.wrappers.nbt.NbtWrapper; + +/** + * Serializes NBT to a base-64 encoded string and back. + * + * @author Kristian + */ +public class NbtTextSerializer { + /** + * A default instance of this serializer. + */ + public static final NbtTextSerializer DEFAULT = new NbtTextSerializer(); + + private NbtBinarySerializer binarySerializer; + + public NbtTextSerializer() { + this(new NbtBinarySerializer()); + } + + /** + * Construct a serializer with a custom binary serializer. + * @param binary - binary serializer. + */ + public NbtTextSerializer(NbtBinarySerializer binary) { + this.binarySerializer = binary; + } + + /** + * Retrieve the binary serializer that is used. + * @return The binary serializer. + */ + public NbtBinarySerializer getBinarySerializer() { + return binarySerializer; + } + + /** + * Serialize a NBT tag to a base-64 encoded string. + * @param value - the NBT tag to serialize. + * @return The NBT tag in base-64 form. + */ + public String serialize(NbtBase value) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutput = new DataOutputStream(outputStream); + + binarySerializer.serialize(value, dataOutput); + + // Serialize that array + return Base64Coder.encodeLines(outputStream.toByteArray()); + } + + /** + * Deserialize a NBT tag from a base-64 encoded string. + * @param input - the base-64 string. + * @return The NBT tag contained in the string. + * @throws IOException If we are unable to parse the input. + */ + public NbtWrapper deserialize(String input) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input)); + + return binarySerializer.deserialize(new DataInputStream(inputStream)); + } + + /** + * Deserialize a NBT compound from a base-64 encoded string. + * @param input - the base-64 string. + * @return The NBT tag contained in the string. + * @throws IOException If we are unable to parse the input. + */ + @SuppressWarnings("rawtypes") + public NbtCompound deserializeCompound(String input) throws IOException { + // I always seem to override generics ... + return (NbtCompound) (NbtBase) deserialize(input); + } + + /** + * Deserialize a NBT list from a base-64 encoded string. + * @param input - the base-64 string. + * @return The NBT tag contained in the string. + * @throws IOException If we are unable to parse the input. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public NbtList deserializeList(String input) throws IOException { + return (NbtList) (NbtBase) deserialize(input); + } +} diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 68783116..5acbb65b 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.9.0 +version: 2.0.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java index b69c29fd..8f98de70 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/MinecraftVersionTest.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import static org.junit.Assert.*; diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java index 7be7e705..75126855 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.concurrency; import static org.junit.Assert.*; diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index bbee0a76..8359c753 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.events; import static org.junit.Assert.*; @@ -32,6 +49,8 @@ import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -233,6 +252,22 @@ public class PacketContainerTest { assertEquals(testValue, worldAccess.read(0)); } + @Test + public void testGetNbtModifier() { + PacketContainer updateTileEntity = new PacketContainer(132); + + NbtCompound compound = NbtFactory.ofCompound("test"); + compound.put("test", "name"); + compound.put(NbtFactory.ofList("ages", 1, 2, 3)); + + updateTileEntity.getNbtModifier().write(0, compound); + + NbtCompound result = (NbtCompound) updateTileEntity.getNbtModifier().read(0); + + assertEquals(compound.getString("test"), result.getString("test")); + assertEquals(compound.getList("ages"), result.getList("ages")); + } + @Test public void testGetDataWatcherModifier() { PacketContainer mobSpawnPacket = new PacketContainer(Packets.Server.MOB_SPAWN); diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java index 775a197e..57280526 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/SortedCopyOnWriteArrayTest.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector; import static org.junit.Assert.*; diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtCompoundTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtCompoundTest.java new file mode 100644 index 00000000..bfeea39d --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtCompoundTest.java @@ -0,0 +1,100 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import static org.junit.Assert.*; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.comphenix.protocol.utility.MinecraftReflection; + +public class NbtCompoundTest { + @BeforeClass + public static void setupBukkit() { + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6"); + } + + @Test + public void testCustomTags() { + NbtCustomTag test = new NbtCustomTag("hello", 12); + + WrappedCompound map = WrappedCompound.fromName("test"); + map.put(test); + + // Note that the custom tag will be cloned + assertEquals(12, map.getInteger("hello")); + } + + /** + * Represents a custom NBT tag. + * + * @author Kristian + * + * @param - the value of the tag. + */ + public static class NbtCustomTag implements NbtBase { + private String name; + private TValue value; + private NbtType type; + + public NbtCustomTag(String name, TValue value) { + if (value == null) + throw new IllegalArgumentException("Cannot create a custom tag from NULL."); + this.value = value; + this.name = name; + this.type = NbtType.getTypeFromClass(value.getClass()); + + } + + @Override + public NbtType getType() { + return type; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public TValue getValue() { + return value; + } + + @Override + public void setValue(TValue newValue) { + this.value = newValue; + } + + @Override + public NbtBase deepClone() { + return new NbtCustomTag(name, value); + } + + @Override + public boolean accept(NbtVisitor visitor) { + return visitor.visit(this); + } + } +} diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java new file mode 100644 index 00000000..c2504df0 --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java @@ -0,0 +1,64 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.wrappers.nbt; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; + +public class NbtFactoryTest { + @BeforeClass + public static void initializeBukkit() { + // Initialize reflection + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6"); + } + + @Test + public void testFromStream() { + WrappedCompound compound = WrappedCompound.fromName("tag"); + + compound.put("name", "Test Testerson"); + compound.put("age", 42); + + compound.put(NbtFactory.ofList("nicknames", "a", "b", "c")); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + DataOutput test = new DataOutputStream(buffer); + compound.write(test); + + ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray()); + DataInput input = new DataInputStream(source); + + NbtCompound cloned = NbtBinarySerializer.DEFAULT.deserializeCompound(input); + + assertEquals(compound.getString("name"), cloned.getString("name")); + assertEquals(compound.getInteger("age"), cloned.getInteger("age")); + assertEquals(compound.getList("nicknames"), cloned.getList("nicknames")); + } +} diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializerTest.java new file mode 100644 index 00000000..916875ab --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/io/NbtConfigurationSerializerTest.java @@ -0,0 +1,38 @@ +package com.comphenix.protocol.wrappers.nbt.io; + +import static org.junit.Assert.*; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; + +public class NbtConfigurationSerializerTest { + @BeforeClass + public static void initializeBukkit() { + // Initialize reflection + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6"); + } + + @SuppressWarnings("unchecked") + @Test + public void testSerialization() { + NbtCompound compound = NbtFactory.ofCompound("hello"); + compound.put("age", (short) 30); + compound.put("name", "test"); + compound.put("values", new int[] { 1, 2, 3}); + compound.put(NbtFactory.ofList("telephone", "12345678", "81549300")); + + compound.put(NbtFactory.ofList("lists", NbtFactory.ofList("", "a", "a", "b", "c"))); + + YamlConfiguration yaml = new YamlConfiguration(); + NbtConfigurationSerializer.DEFAULT.serialize(compound, yaml); + + NbtCompound result = NbtConfigurationSerializer.DEFAULT.deserializeCompound(yaml, "hello"); + + assertEquals(compound, result); + } +}