Merge branch 'master' into gh-pages

This commit is contained in:
Kristian S. Stangeland 2013-01-10 00:52:35 +01:00
commit 9778ed6fc5
38 changed files with 4305 additions and 349 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dependency-reduced-pom.xml

View File

@ -1,218 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>1.9.0</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<developers>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
</scm>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<defaultGoal>clean install</defaultGoal>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.bukkit:craftbukkit</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.4.6-R0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>powermock-module-junit4-common</artifactId>
<groupId>org.powermock</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>powermock-api-support</artifactId>
<groupId>org.powermock</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
</plugins>
</reporting>
<distributionManagement>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>comphenix-snapshots</id>
<name>Comphenix Maven Snapshots</name>
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<properties>
<powermock.version>1.5</powermock.version>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
</project>

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.9.0</version>
<version>2.0.0</version>
<packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description>

View File

@ -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<byte[]> 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<NbtBase<?>> 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.
* <p>

View File

@ -70,85 +70,73 @@ import java.util.UUID;
import java.util.logging.Level;
/**
* <p>
* The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.
* </p>
* <p>
* Public methods provided by this class:
* </p>
* <p> The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. </p> <p>
* Public methods provided by this class: </p>
* <code>
* Graph createGraph(String name); <br/>
* void addCustomData(Metrics.Plotter plotter); <br/>
* void addCustomData(BukkitMetrics.Plotter plotter); <br/>
* void start(); <br/>
* </code>
*/
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<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* 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 {
}
/**
* <p>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:</p>
* <p>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:</p>
* <code>
* 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 <b>unmodifiable</b> 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<Plotter> 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();
}
}
}

View File

@ -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();
}
};
}
}

View File

@ -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<Object> previous = new HashSet<Object>();

View File

@ -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<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> 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<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> 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();
}
}

View File

@ -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<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
private Map<StructureKey, Class> compiledCache = new ConcurrentHashMap<StructureKey, Class>();
// 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 <TField> boolean lookupClassLoader(StructureModifier<TField> 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.
* <p>
@ -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 <TField> Class<?> generateClass(StructureModifier<TField> 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);

View File

@ -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.

View File

@ -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.
* <p>
* 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.
* <p>
* 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());
}
}

View File

@ -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<NbtBase<?>> getNbtConverter() {
return getIgnoreNull(new EquivalentConverter<NbtBase<?>>() {
@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<NbtBase<?>> getSpecificType() {
// Damn you Java AGAIN
Class<?> dummy = NbtBase.class;
return (Class<NbtBase<?>>) dummy;
}
});
}
/**
* Retrieve a converter for NMS entities and Bukkit entities.
* @param world - the current world.

View File

@ -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 <VInner> - the first type.
* @param <VOuter> - the second type.
*/
abstract class AbstractConverted<VInner, VOuter> {
/**
* 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<VInner, VOuter> getOuterConverter() {
return new Function<VInner, VOuter>() {
@Override
public VOuter apply(@Nullable VInner param) {
return toOuter(param);
}
};
}
}

View File

@ -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 <VInner> - type of the element in the inner invisible collection.
* @param <VOuter> - type of the elements publically accessible in the outer collection.
*/
abstract class ConvertedCollection<VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Collection<VOuter> {
// Inner collection
private Collection<VInner> inner;
public ConvertedCollection(Collection<VInner> inner) {
this.inner = inner;
}
@Override
public boolean add(VOuter e) {
return inner.add(toInner(e));
}
@Override
public boolean addAll(Collection<? extends VOuter> 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<VOuter> 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<VInner> 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> 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;
}
}

View File

@ -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 <VInner> - type of the items in the inner invisible list.
* @param <VOuter> - type of the items publically accessible in the outer list.
*/
abstract class ConvertedList<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements List<VOuter> {
private List<VInner> inner;
public ConvertedList(List<VInner> 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<? extends VOuter> 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<VOuter> listIterator() {
return listIterator(0);
}
@Override
public ListIterator<VOuter> listIterator(int index) {
final ListIterator<VInner> innerIterator = inner.listIterator(index);
return new ListIterator<VOuter>() {
@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<VOuter> subList(int fromIndex, int toIndex) {
return new ConvertedList<VInner, VOuter>(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<VOuter, VInner> getInnerCollection(Collection c) {
return new ConvertedCollection<VOuter, VInner>(c) {
@Override
protected VOuter toInner(VInner outer) {
return ConvertedList.this.toOuter(outer);
}
@Override
protected VInner toOuter(VOuter inner) {
return ConvertedList.this.toInner(inner);
}
};
}
}

View File

@ -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 <VInner> - type of the value in the entries in the inner invisible map.
* @param <VOuter> - type of the value in the entries publically accessible in the outer map.
*/
abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverted<VInner, VOuter> implements Map<Key, VOuter> {
// Inner map
private Map<Key, VInner> inner;
public ConvertedMap(Map<Key, VInner> 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<Entry<Key, VOuter>> entrySet() {
return new ConvertedSet<Entry<Key,VInner>, Entry<Key,VOuter>>(inner.entrySet()) {
@Override
protected Entry<Key, VInner> toInner(final Entry<Key, VOuter> outer) {
return new Entry<Key, VInner>() {
@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<Key, VOuter> toOuter(final Entry<Key, VInner> inner) {
return new Entry<Key, VOuter>() {
@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<Key> keySet() {
return inner.keySet();
}
@Override
public VOuter put(Key key, VOuter value) {
return toOuter(inner.put(key, toInner(value)));
}
@Override
public void putAll(Map<? extends Key, ? extends VOuter> m) {
for (Entry<? extends Key, ? extends VOuter> 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<VOuter> values() {
return new ConvertedCollection<VInner, VOuter>(inner.values()) {
@Override
protected VOuter toOuter(VInner inner) {
return ConvertedMap.this.toOuter(inner);
}
@Override
protected VInner toInner(VOuter outer) {
return ConvertedMap.this.toInner(outer);
}
};
}
}

View File

@ -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 <VInner> - type of the element in the inner invisible set.
* @param <VOuter> - type of the elements publically accessible in the outer set.
*/
abstract class ConvertedSet<VInner, VOuter> extends ConvertedCollection<VInner, VOuter> implements Set<VOuter> {
public ConvertedSet(Collection<VInner> inner) {
super(inner);
}
}

View File

@ -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.
* <p>
* Use {@link NbtFactory} to load or create an instance.
*
* @author Kristian
* @param <TType> - type of the value that is stored.
*/
public interface NbtBase<TType> {
/**
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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}.
* <p>
* 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<TType> deepClone();
}

View File

@ -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.
* <p>
* Use {@link NbtFactory} to load or create an instance.
* <p>
* 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<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>> {
/**
* 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<String> 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 <T> NbtBase<T> 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 <T> NbtCompound put(NbtBase<T> 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 <T> NbtList<T> 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 <T> NbtList<T> getListOrDefault(String key);
/**
* Associate a NBT list with the given key.
* @param list - the list value.
* @return This current compound, for chaining.
*/
public abstract <T> NbtCompound put(NbtList<T> 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 <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> list);
/**
* Retrieve an iterator view of the NBT tags stored in this compound.
* @return The tags stored in this compound.
*/
public abstract Iterator<NbtBase<?>> iterator();
}

View File

@ -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<Object> 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.
* <p>
* 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 <T> NbtWrapper<T> fromBase(NbtBase<T> base) {
if (base instanceof NbtWrapper) {
return (NbtWrapper<T>) 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<String, NbtBase<?>>) value);
return (NbtWrapper<T>) copy;
} else if (base.getType() == NbtType.TAG_LIST) {
// As above
NbtList<T> copy = WrappedList.fromName(base.getName());
copy.setValue((List<NbtBase<T>>) base.getValue());
return (NbtWrapper<T>) copy;
} else {
// Copy directly
NbtWrapper<T> 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.
* <p>
* 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<Object>(nmsStack.getClass(), Object.class, false);
}
// Use the first and best NBT tag
StructureModifier<NbtBase<?>> 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 <T> NbtWrapper<T> fromNMS(Object handle) {
WrappedElement<T> partial = new WrappedElement<T>(handle);
// See if this is actually a compound tag
if (partial.getType() == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) 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<String> 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<Byte> 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<Short> 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<Integer> 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<Long> 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<Float> 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<Double> 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<byte[]> 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<int[]> 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<? extends NbtBase<?>> 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 <T> NbtList<T> 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 <T> NbtList<T> ofList(String name, Collection<? extends T> 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 <T> NbtWrapper<T> 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<T>) new WrappedCompound(handle);
else if (type == NbtType.TAG_LIST)
return (NbtWrapper<T>) new WrappedList(handle);
else
return new WrappedElement<T>(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 <T> NbtWrapper<T> ofWrapper(NbtType type, String name, T value) {
NbtWrapper<T> 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 <T> NbtWrapper<T> ofWrapper(Class<?> type, String name, T value) {
return ofWrapper(NbtType.getTypeFromClass(type), name, value);
}
}

View File

@ -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.
* <p>
* Use {@link NbtFactory} to load or create an instance.
* <p>
* 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 <TType> - the value type of each NBT tag.
*/
public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<TType> {
/**
* The name of every NBT tag in a list.
*/
public static String EMPTY_NAME = "";
/**
* Get the type of each element.
* <p>
* 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.
* <p>
* 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<TType> 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<NbtBase<TType>> asCollection();
/**
* Iterate over all the elements in this list.
*/
public abstract Iterator<TType> iterator();
}

View File

@ -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<Class<?>, NbtType> classLookup;
static {
NbtType[] values = values();
lookup = new NbtType[values.length];
classLookup = new HashMap<Class<?>, 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);
}
}
}

View File

@ -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);
}

View File

@ -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.
* <p>
* Use {@link NbtFactory} to load or create instances.
*
* @author Kristian
*
* @param <TType> - type of the value that is stored.
*/
public interface NbtWrapper<TType> extends NbtBase<TType> {
/**
* 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);
}

View File

@ -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<Map<String, NbtBase<?>>>, Iterable<NbtBase<?>>, NbtCompound {
// A list container
private WrappedElement<Map<String, Object>> container;
// Saved wrapper map
private ConvertedMap<String, Object, NbtBase<?>> 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.<Map<String, NbtBase<?>>>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<? extends NbtBase<?>> 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<Map<String,Object>>(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<String> getKeys() {
return getValue().keySet();
}
@Override
public Map<String, NbtBase<?>> getValue() {
// Return a wrapper map
if (savedMap == null) {
savedMap = new ConvertedMap<String, Object, NbtBase<?>>(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<String, NbtBase<?>> newValue) {
// Write all the entries
for (Map.Entry<String, NbtBase<?>> 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 <T> NbtBase<T> getValue(String key) {
return (NbtBase<T>) 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 <T> NbtBase<T> getValueExact(String key) {
NbtBase<T> 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<Map<String, 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 <T> NbtCompound put(NbtBase<T> 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 <T> NbtList<T> 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 <T> NbtList<T> getListOrDefault(String key) {
return (NbtList<T>) 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 <T> NbtCompound put(NbtList<T> 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 <T> NbtCompound put(String key, Collection<? extends NbtBase<T>> 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<NbtBase<?>> 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();
}
}

View File

@ -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 <TType> - type of the value field.
*/
class WrappedElement<TType> implements NbtWrapper<TType> {
// Structure modifier for the base class
private static volatile StructureModifier<Object> 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<String> getBaseModifier() {
if (baseModifier == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// This will be the same for all classes, so we'll share modifier
baseModifier = new StructureModifier<Object>(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<TType> 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<Object> getCurrentBaseModifier() {
int index = getType().ordinal();
StructureModifier<Object> modifier = (StructureModifier<Object>) modifiers[index];
// Double checked locking
if (modifier == null) {
synchronized (this) {
if (modifiers[index] == null) {
modifiers[index] = new StructureModifier<Object>(handle.getClass(), MinecraftReflection.getNBTBaseClass(), false);
}
modifier = (StructureModifier<Object>) 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().<Byte>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().<Byte>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<TType> 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();
}
}

View File

@ -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 <TType> - the type of the value in each NBT sub element.
*/
class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<TType>, NbtList<TType> {
// A list container
private WrappedElement<List<Object>> container;
// Saved wrapper list
private ConvertedList<Object, NbtBase<TType>> 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 <T> NbtList<T> fromName(String name) {
return (NbtList<T>) NbtFactory.<List<NbtBase<T>>>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 <T> NbtList<T> fromArray(String name, T... elements) {
NbtList<T> 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 <T> NbtList<T> fromList(String name, Collection<? extends T> elements) {
NbtList<T> 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<List<Object>>(handle);
this.elementType = container.getSubType();
}
@Override
public boolean accept(NbtVisitor visitor) {
// Enter this node?
if (visitor.visitEnter(this)) {
for (NbtBase<TType> 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<NbtBase<TType>> getValue() {
if (savedList == null) {
savedList = new ConvertedList<Object, NbtBase<TType>>(container.getValue()) {
// Check and see if the element is valid
private void verifyElement(NbtBase<TType> 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<TType> e) {
verifyElement(e);
return super.add(e);
}
@Override
public void add(int index, NbtBase<TType> element) {
verifyElement(element);
super.add(index, element);
}
@Override
public boolean addAll(Collection<? extends NbtBase<TType>> c) {
boolean result = false;
for (NbtBase<TType> element : c) {
add(element);
result = true;
}
return result;
}
@Override
protected Object toInner(NbtBase<TType> outer) {
if (outer == null)
return null;
return NbtFactory.fromBase(outer).getHandle();
}
@Override
protected NbtBase<TType> 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<List<NbtBase<TType>>> 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<TType>) value);
} else {
// Just add it
add((NbtBase<TType>) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value));
}
}
@Override
public void add(NbtBase<TType> element) {
getValue().add(element);
}
@Override
@SuppressWarnings("unchecked")
public void add(String value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(byte value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(short value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(int value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(long value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(double value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(byte[] value) {
add((NbtBase<TType>) NbtFactory.of(EMPTY_NAME, value));
}
@Override
@SuppressWarnings("unchecked")
public void add(int[] value) {
add((NbtBase<TType>) 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<NbtBase<TType>> asCollection() {
return getValue();
}
@Override
public void setValue(List<NbtBase<TType>> newValue) {
NbtBase<TType> lastElement = null;
List<Object> list = container.getValue();
list.clear();
// Set each underlying element
for (NbtBase<TType> 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<TType> other = (WrappedList<TType>) obj;
return container.equals(other.container);
}
return false;
}
@Override
public int hashCode() {
return container.hashCode();
}
@Override
public Iterator<TType> iterator() {
return Iterables.transform(getValue(), new Function<NbtBase<TType>, TType>() {
@Override
public TType apply(@Nullable NbtBase<TType> 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);
}
}

View File

@ -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 <TType> void serialize(NbtBase<TType> 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 <TType> NbtWrapper<TType> 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 <T> NbtList<T> deserializeList(DataInput source) {
return (NbtList<T>) (NbtBase) deserialize(source);
}
}

View File

@ -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.
* <p>
* 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 <TType> void serialize(NbtBase<TType> value, final ConfigurationSection destination) {
value.accept(new NbtVisitor() {
private ConfigurationSection current = destination;
// The current list we're working on
private List<Object> currentList;
// Store the index of a configuration section that works like a list
private Map<ConfigurationSection, Integer> 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 <TType> NbtWrapper<TType> deserialize(ConfigurationSection root, String nodeName) {
return (NbtWrapper<TType>) 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 <T> NbtList<T> deserializeList(YamlConfiguration root, String nodeName) {
return (NbtList<T>) 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<Object> list = NbtFactory.ofList(decoded[0]);
ConfigurationSection section = (ConfigurationSection) node;
List<String> sorted = sortSet(section.getKeys(false));
// Read everything in order
for (String key : sorted) {
NbtBase<Object> base = (NbtBase<Object>) 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<Object> list = NbtFactory.ofList(decoded[0]);
list.setElementType(type);
for (Object value : (List<Object>) 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<String> sortSet(Set<String> unsorted) {
// Convert to integers
List<String> sorted = new ArrayList<String>(unsorted);
Collections.sort(sorted, new Comparator<String>() {
@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.
* <p>
* 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 };
}
}

View File

@ -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 <TType> String serialize(NbtBase<TType> 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 <TType> NbtWrapper<TType> 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 <T> NbtList<T> deserializeList(String input) throws IOException {
return (NbtList<T>) (NbtBase) deserialize(input);
}
}

View File

@ -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

View File

@ -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.*;

View File

@ -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.*;

View File

@ -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);

View File

@ -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.*;

View File

@ -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<Integer> test = new NbtCustomTag<Integer>("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 <TValue> - the value of the tag.
*/
public static class NbtCustomTag<TValue> implements NbtBase<TValue> {
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<TValue> deepClone() {
return new NbtCustomTag<TValue>(name, value);
}
@Override
public boolean accept(NbtVisitor visitor) {
return visitor.visit(this);
}
}
}

View File

@ -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"));
}
}

View File

@ -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);
}
}