Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
044a58f657
@ -15,9 +15,15 @@
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>net.sourceforge.metrics.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>net.sourceforge.metrics.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>1.8.0</version>
|
||||
<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>
|
||||
@ -139,7 +139,7 @@
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.4.5-R0.3-SNAPSHOT</version>
|
||||
<version>1.4.6-R0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -154,6 +154,36 @@
|
||||
</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>
|
||||
@ -181,6 +211,7 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<powermock.version>1.5</powermock.version>
|
||||
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
|
||||
</properties>
|
||||
</project>
|
||||
|
@ -2,12 +2,13 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<version>1.9.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
|
||||
<powermock.version>1.5</powermock.version>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
@ -202,7 +203,7 @@
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.4.5-R0.3-SNAPSHOT</version>
|
||||
<version>1.4.6-R0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -211,5 +212,23 @@
|
||||
<version>4.10</version>
|
||||
<scope>test</scope>
|
||||
</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>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -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 java.lang.reflect.Field;
|
||||
@ -13,7 +30,7 @@ import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
@ -48,7 +65,7 @@ class CleanupStaticMembers {
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectCloner.class, Packets.Server.class, Packets.Client.class,
|
||||
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
|
||||
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class
|
||||
};
|
||||
|
||||
|
@ -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 org.bukkit.ChatColor;
|
||||
|
@ -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 java.lang.reflect.InvocationTargetException;
|
||||
|
@ -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 org.bukkit.ChatColor;
|
||||
|
@ -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 java.util.regex.Matcher;
|
||||
@ -7,6 +24,7 @@ import org.bukkit.Server;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
@ -23,6 +41,9 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
private final int minor;
|
||||
private final int build;
|
||||
|
||||
// The development stage
|
||||
private final String development;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
* @param server - the Bukkit server that will be used to examine the MC version.
|
||||
@ -36,11 +57,13 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
* @param versionOnly - the version in text form.
|
||||
*/
|
||||
public MinecraftVersion(String versionOnly) {
|
||||
int[] numbers = parseVersion(versionOnly);
|
||||
String[] section = versionOnly.split("-");
|
||||
int[] numbers = parseVersion(section[0]);
|
||||
|
||||
this.major = numbers[0];
|
||||
this.minor = numbers[1];
|
||||
this.build = numbers[2];
|
||||
this.development = section.length > 1 ? section[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,9 +73,21 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
* @param build - build version number.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build) {
|
||||
this(major, minor, build, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object directly.
|
||||
* @param major - major version number.
|
||||
* @param minor - minor version number.
|
||||
* @param build - build version number.
|
||||
* @param development - development stage.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build, String development) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.build = build;
|
||||
this.development = development;
|
||||
}
|
||||
|
||||
private int[] parseVersion(String version) {
|
||||
@ -93,12 +128,23 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
return build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the development stage.
|
||||
* @return Development stage, or NULL if this is a release.
|
||||
*/
|
||||
public String getDevelopmentStage() {
|
||||
return development;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the version String (major.minor.build) only.
|
||||
* @return A normal version string.
|
||||
*/
|
||||
public String getVersion() {
|
||||
if (development == null)
|
||||
return String.format("%s.%s.%s", major, minor, build);
|
||||
else
|
||||
return String.format("%s.%s.%s-%s", major, minor, build, development);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,6 +156,8 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
compare(major, o.major).
|
||||
compare(minor, o.minor).
|
||||
compare(build, o.build).
|
||||
// No development String means it's a release
|
||||
compare(development, o.development, Ordering.natural().nullsLast()).
|
||||
result();
|
||||
}
|
||||
|
||||
@ -125,7 +173,8 @@ class MinecraftVersion implements Comparable<MinecraftVersion> {
|
||||
|
||||
return major == other.major &&
|
||||
minor == other.minor &&
|
||||
build == other.build;
|
||||
build == other.build &&
|
||||
Objects.equal(development, other.development);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -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 java.io.File;
|
||||
|
@ -17,11 +17,14 @@
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@ -52,7 +55,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with,
|
||||
*/
|
||||
private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.5";
|
||||
private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.6";
|
||||
|
||||
/**
|
||||
* The number of milliseconds per second.
|
||||
@ -65,7 +68,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
private static PacketFilterManager protocolManager;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
private static ErrorReporter reporter;
|
||||
|
||||
// Metrics and statistisc
|
||||
private Statistics statistisc;
|
||||
@ -97,6 +100,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
private CommandProtocol commandProtocol;
|
||||
private CommandPacket commandPacket;
|
||||
|
||||
// Whether or not disable is not needed
|
||||
private boolean skipDisable;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Load configuration
|
||||
@ -104,7 +110,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// Add global parameters
|
||||
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
|
||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||
reporter = detailedReporter;
|
||||
|
||||
try {
|
||||
@ -121,6 +126,12 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
|
||||
try {
|
||||
// Check for other versions
|
||||
checkConflictingVersions();
|
||||
|
||||
// Set updater
|
||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||
|
||||
unhookTask = new DelayedSingleTask(this);
|
||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter);
|
||||
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||
@ -253,6 +264,45 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConflictingVersions() {
|
||||
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion());
|
||||
MinecraftVersion newestVersion = null;
|
||||
|
||||
try {
|
||||
// Scan the plugin folder for newer versions of ProtocolLib
|
||||
File pluginFolder = new File("plugins/");
|
||||
|
||||
for (File candidate : pluginFolder.listFiles()) {
|
||||
if (candidate.isFile()) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
|
||||
if (match.matches()) {
|
||||
MinecraftVersion version = new MinecraftVersion(match.group(1));
|
||||
|
||||
if (newestVersion == null || newestVersion.compareTo(version) < 0) {
|
||||
newestVersion = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
reporter.reportWarning(this, "Unable to detect conflicting plugin versions.", e);
|
||||
}
|
||||
|
||||
// See if the newest version is actually higher
|
||||
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
|
||||
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
|
||||
skipDisable = true;
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
|
||||
newestVersion.getVersion(), currentVersion.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(String name, CommandExecutor executor) {
|
||||
try {
|
||||
if (executor == null)
|
||||
@ -331,6 +381,10 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (skipDisable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable compiler
|
||||
if (backgroundCompiler != null) {
|
||||
backgroundCompiler.shutdownAll();
|
||||
@ -353,6 +407,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
protocolManager.close();
|
||||
protocolManager = null;
|
||||
statistisc = null;
|
||||
reporter = null;
|
||||
|
||||
// Leaky ClassLoader begone!
|
||||
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
|
||||
@ -373,6 +428,14 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager, or NULL if it has been disabled.
|
||||
|
@ -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 java.util.ArrayList;
|
||||
|
@ -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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -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 java.util.HashSet;
|
||||
|
@ -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 java.util.Collection;
|
||||
|
@ -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.error;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@ -6,6 +23,9 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -15,6 +35,7 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
@ -35,10 +56,14 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
// We don't want to spam the server
|
||||
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
|
||||
|
||||
// Prevent spam per plugin too
|
||||
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
|
||||
|
||||
protected String prefix;
|
||||
protected String supportURL;
|
||||
|
||||
protected int errorCount;
|
||||
protected AtomicInteger internalErrorCount = new AtomicInteger();
|
||||
|
||||
protected int maxErrorCount;
|
||||
protected Logger logger;
|
||||
|
||||
@ -97,8 +122,7 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
reportMinimal(sender, methodName, error);
|
||||
|
||||
if (reportMinimalNoSpam(sender, methodName, error)) {
|
||||
// Print parameters, if they are given
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
logger.log(Level.SEVERE, " Parameters:");
|
||||
@ -109,11 +133,54 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " +
|
||||
PacketAdapter.getPluginName(sender), error);
|
||||
reportMinimalNoSpam(sender, methodName, error);
|
||||
}
|
||||
|
||||
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
|
||||
String pluginName = PacketAdapter.getPluginName(sender);
|
||||
AtomicInteger counter = warningCount.get(pluginName);
|
||||
|
||||
// Thread safe pattern
|
||||
if (counter == null) {
|
||||
AtomicInteger created = new AtomicInteger();
|
||||
counter = warningCount.putIfAbsent(pluginName, created);
|
||||
|
||||
if (counter == null) {
|
||||
counter = created;
|
||||
}
|
||||
}
|
||||
|
||||
final int errorCount = counter.incrementAndGet();
|
||||
|
||||
// See if we should print the full error
|
||||
if (errorCount < getMaxErrorCount()) {
|
||||
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Nope - only print the error count occationally
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " +
|
||||
methodName + " for " + pluginName, error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given number is a power of two.
|
||||
* <p>
|
||||
* That is, if there exists an N such that 2^N = number.
|
||||
* @param number - the number to check.
|
||||
* @return TRUE if the given number is a power of two, FALSE otherwise.
|
||||
*/
|
||||
private boolean isPowerOfTwo(int number) {
|
||||
return (number & (number - 1)) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,13 +204,19 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
|
||||
|
||||
final Plugin plugin = pluginReference.get();
|
||||
final int errorCount = internalErrorCount.incrementAndGet();
|
||||
|
||||
// Do not overtly spam the server!
|
||||
if (++errorCount > maxErrorCount) {
|
||||
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
|
||||
logger.severe(maxReached);
|
||||
if (errorCount > getMaxErrorCount()) {
|
||||
// Only allow the error count at rare occations
|
||||
if (isPowerOfTwo(errorCount)) {
|
||||
// Permit it - but print the number of exceptions first
|
||||
reportWarning(this, "Internal exception count: " + errorCount + "!");
|
||||
} else {
|
||||
// NEVER SPAM THE CONSOLE
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StringWriter text = new StringWriter();
|
||||
PrintWriter writer = new PrintWriter(text);
|
||||
@ -230,12 +303,15 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
|
||||
} catch (Throwable ex) {
|
||||
// Apache is probably missing
|
||||
logger.warning("Cannot find Apache Commons. Object introspection disabled.");
|
||||
apacheCommonsMissing = true;
|
||||
}
|
||||
|
||||
// Just use toString()
|
||||
return String.format("%s", value);
|
||||
// Use our custom object printer instead
|
||||
try {
|
||||
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
|
||||
} catch (IllegalAccessException e) {
|
||||
return "[Error: " + e.getMessage() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,11 +325,11 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
return internalErrorCount.get();
|
||||
}
|
||||
|
||||
public void setErrorCount(int errorCount) {
|
||||
this.errorCount = errorCount;
|
||||
internalErrorCount.set(errorCount);
|
||||
}
|
||||
|
||||
public int getMaxErrorCount() {
|
||||
|
@ -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.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
@ -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 java.util.logging.Level;
|
||||
|
@ -17,20 +17,21 @@
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.entity.Entity;
|
||||
@ -39,12 +40,22 @@ import org.bukkit.inventory.ItemStack;
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.Cloner;
|
||||
import com.comphenix.protocol.reflect.cloning.CollectionCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.FieldCloner;
|
||||
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.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
@ -69,6 +80,28 @@ public class PacketContainer implements Serializable {
|
||||
private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap();
|
||||
private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap();
|
||||
|
||||
// Used to clone packets
|
||||
private static final AggregateCloner DEEP_CLONER = AggregateCloner.newBuilder().
|
||||
instanceProvider(DefaultInstances.DEFAULT).
|
||||
andThen(BukkitCloner.class).
|
||||
andThen(ImmutableDetector.class).
|
||||
andThen(CollectionCloner.class).
|
||||
andThen(getSpecializedDeepClonerFactory()).
|
||||
build();
|
||||
|
||||
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner.newBuilder().
|
||||
instanceProvider(DefaultInstances.DEFAULT).
|
||||
andThen(new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
// Use a default writer with no concept of cloning
|
||||
writer = new ObjectWriter();
|
||||
}};
|
||||
}
|
||||
}).
|
||||
build();
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param id - ID of the packet to create.
|
||||
@ -241,12 +274,12 @@ public class PacketContainer implements Serializable {
|
||||
BukkitConverters.getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
|
||||
public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
|
||||
Object[] result = new Object[specific.length];
|
||||
Class<?> nmsStack = MinecraftReflection.getItemStackClass();
|
||||
Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = stackConverter.getGeneric(
|
||||
MinecraftReflection.getItemStackClass(), specific[i]);
|
||||
result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -364,40 +397,54 @@ public class PacketContainer implements Serializable {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow copy of the current packet.
|
||||
* <p>
|
||||
* This merely writes the content of each field to the new class directly,
|
||||
* without performing any expensive copies.
|
||||
*
|
||||
* @return A shallow copy of the current packet.
|
||||
*/
|
||||
public PacketContainer shallowClone() {
|
||||
Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
|
||||
return new PacketContainer(getID(), clonedPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the current packet.
|
||||
* <p>
|
||||
* This will perform a full copy of the entire object tree, only skipping
|
||||
* known immutable objects and primitive types.
|
||||
* <p>
|
||||
* Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
|
||||
*
|
||||
* @return A deep copy of the current packet.
|
||||
*/
|
||||
public PacketContainer deepClone() {
|
||||
ObjectOutputStream output = null;
|
||||
ObjectInputStream input = null;
|
||||
|
||||
try {
|
||||
// Use a small buffer of 32 bytes initially.
|
||||
ByteArrayOutputStream bufferOut = new ByteArrayOutputStream();
|
||||
output = new ObjectOutputStream(bufferOut);
|
||||
output.writeObject(this);
|
||||
|
||||
ByteArrayInputStream bufferIn = new ByteArrayInputStream(bufferOut.toByteArray());
|
||||
input = new ObjectInputStream(bufferIn);
|
||||
return (PacketContainer) input.readObject();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unexpected error occured during object cloning.", e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Cannot happen
|
||||
throw new IllegalStateException("Unexpected failure with serialization.", e);
|
||||
} finally {
|
||||
try {
|
||||
if (output != null)
|
||||
output.close();
|
||||
if (input != null)
|
||||
input.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
// STOP IT
|
||||
Object clonedPacket = DEEP_CLONER.clone(getHandle());
|
||||
return new PacketContainer(getID(), clonedPacket);
|
||||
}
|
||||
|
||||
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
|
||||
private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
|
||||
// Look at what you've made me do Java, look at it!!
|
||||
return new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
this.writer = new ObjectWriter() {
|
||||
protected void transformField(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
// No need to clone inflated buffers
|
||||
if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
|
||||
modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
|
||||
else
|
||||
defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
|
||||
};
|
||||
};
|
||||
}};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
|
@ -17,14 +17,18 @@
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
||||
@ -38,41 +42,33 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitUnwrapper implements Unwrapper {
|
||||
|
||||
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
|
||||
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
|
||||
// Special cases
|
||||
if (wrappedObject == null) {
|
||||
// Special case
|
||||
if (wrappedObject == null)
|
||||
return null;
|
||||
} else if (wrappedObject instanceof Collection) {
|
||||
Class<?> currentClass = wrappedObject.getClass();
|
||||
|
||||
// Next, check for types that doesn't have a getHandle()
|
||||
if (wrappedObject instanceof Collection) {
|
||||
return handleCollection((Collection<Object>) wrappedObject);
|
||||
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> currentClass = wrappedObject.getClass();
|
||||
Method cachedMethod = initializeCache(currentClass);
|
||||
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
||||
|
||||
try {
|
||||
// Retrieve the handle
|
||||
if (cachedMethod != null)
|
||||
return cachedMethod.invoke(wrappedObject);
|
||||
if (specificUnwrapper != null)
|
||||
return specificUnwrapper.unwrapItem(wrappedObject);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Impossible
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is REALLY bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a collection of items
|
||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -91,24 +87,94 @@ public class BukkitUnwrapper implements Unwrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private Method initializeCache(Class<?> type) {
|
||||
|
||||
/**
|
||||
* Retrieve a cached class unwrapper for the given class.
|
||||
* @param type - the type of the class.
|
||||
* @return An unwrapper for the given class.
|
||||
*/
|
||||
private Unwrapper getSpecificUnwrapper(Class<?> type) {
|
||||
// See if we're already determined this
|
||||
if (cache.containsKey(type)) {
|
||||
if (unwrapperCache.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return cache.get(type);
|
||||
return unwrapperCache.get(type);
|
||||
}
|
||||
|
||||
try {
|
||||
Method find = type.getMethod("getHandle");
|
||||
final Method find = type.getMethod("getHandle");
|
||||
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
cache.put(type, find);
|
||||
return find;
|
||||
Unwrapper methodUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
|
||||
try {
|
||||
return find.invoke(wrappedObject);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Illegal argument.", e, wrappedObject, find);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Should not occur either
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is really bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
unwrapperCache.put(type, methodUnwrapper);
|
||||
return methodUnwrapper;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName());
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Try getting the field unwrapper too
|
||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||
|
||||
if (fieldUnwrapper != null)
|
||||
return fieldUnwrapper;
|
||||
else
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName());
|
||||
}
|
||||
|
||||
// Default method
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached unwrapper using the handle field.
|
||||
* @param type - a cached field unwrapper.
|
||||
* @return The cached field unwrapper.
|
||||
*/
|
||||
private Unwrapper getFieldUnwrapper(Class<?> type) {
|
||||
final Field find = FieldUtils.getField(type, "handle", true);
|
||||
|
||||
// See if we succeeded
|
||||
if (find != null) {
|
||||
Unwrapper fieldUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
return FieldUtils.readField(find, wrappedObject, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Cannot read field 'handle'.", e, wrappedObject, find.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unwrapperCache.put(type, fieldUnwrapper);
|
||||
return fieldUnwrapper;
|
||||
|
||||
} else {
|
||||
// Inform about this too
|
||||
ProtocolLibrary.getErrorReporter().reportDetailed(
|
||||
this, "Could not find field 'handle'.",
|
||||
new Exception("Unable to find 'handle'"), type.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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 org.bukkit.plugin.Plugin;
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -182,6 +182,22 @@ class MinecraftRegistry {
|
||||
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet ID of a given packet.
|
||||
* @param packet - the type of packet to check.
|
||||
* @return The ID of the given packet.
|
||||
* @throws IllegalArgumentException If this is not a valid packet.
|
||||
*/
|
||||
public static int getPacketID(Class<?> packet) {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("Packet type class cannot be NULL.");
|
||||
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
|
||||
throw new IllegalArgumentException("Type must be a packet.");
|
||||
|
||||
// The registry contains both the overridden and original packets
|
||||
return getPacketToID().get(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first superclass that is not a CBLib proxy object.
|
||||
* @param clazz - the class whose hierachy we're going to search through.
|
||||
|
@ -64,6 +64,27 @@ public class StructureCache {
|
||||
return getStructure(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType) {
|
||||
// Compile structures by default
|
||||
return getStructure(packetType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
* @param packetType - packet type.
|
||||
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
|
||||
// Get the ID from the class
|
||||
return getStructure(MinecraftRegistry.getPacketID(packetType), compile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet id.
|
||||
* @param id - packet ID.
|
||||
|
@ -29,7 +29,7 @@ import org.bukkit.Server;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
@ -77,7 +77,8 @@ class InjectedServerConnection {
|
||||
return;
|
||||
|
||||
if (minecraftServerField == null)
|
||||
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
|
||||
minecraftServerField = FuzzyReflection.fromObject(server, true).
|
||||
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
|
||||
|
||||
try {
|
||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||
@ -212,13 +213,16 @@ class InjectedServerConnection {
|
||||
*/
|
||||
private static final long serialVersionUID = 2070481080950500367L;
|
||||
|
||||
// Object writer we'll use
|
||||
private final ObjectWriter writer = new ObjectWriter();
|
||||
|
||||
@Override
|
||||
protected void onReplacing(Object inserting, Object replacement) {
|
||||
// Is this a normal Minecraft object?
|
||||
if (!(inserting instanceof Factory)) {
|
||||
// If so, copy the content of the old element to the new
|
||||
try {
|
||||
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
|
||||
writer.copyTo(inserting, replacement, inserting.getClass());
|
||||
} catch (Throwable e) {
|
||||
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
|
||||
" to new.", e, inserting, replacement);
|
||||
|
@ -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.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -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.player;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@ -8,6 +25,7 @@ import org.bukkit.entity.Player;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
@ -62,9 +80,9 @@ class NetLoginInjector {
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Minecraft can't handle this, so we'll deal with it here
|
||||
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
|
||||
reporter.reportDetailed(this, "Unable to hook " +
|
||||
MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting);
|
||||
return inserting;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +121,8 @@ class NetLoginInjector {
|
||||
|
||||
} catch (Throwable e) {
|
||||
// Don't leak this to Minecraft
|
||||
reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing);
|
||||
reporter.reportDetailed(this, "Cannot cleanup " +
|
||||
MinecraftReflection.getNetLoginHandlerName() + ".", e, removing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
@ -66,6 +66,9 @@ public class NetworkServerInjector extends PlayerInjector {
|
||||
// Whether or not the player has disconnected
|
||||
private boolean hasDisconnected;
|
||||
|
||||
// Used to copy fields
|
||||
private final ObjectWriter writer = new ObjectWriter();
|
||||
|
||||
public NetworkServerInjector(
|
||||
ClassLoader classLoader, ErrorReporter reporter, Player player,
|
||||
ListenerInvoker invoker, IntegerSet sendingFilters,
|
||||
@ -141,7 +144,8 @@ public class NetworkServerInjector extends PlayerInjector {
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
|
||||
"Cannot hook player: Unable to find a valid constructor for the "
|
||||
+ MinecraftReflection.getNetServerHandlerClass().getName() + " object.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +251,7 @@ public class NetworkServerInjector extends PlayerInjector {
|
||||
@Override
|
||||
protected void cleanHook() {
|
||||
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||
ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
|
||||
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
|
||||
serverHandlerRef.revertValue();
|
||||
|
||||
try {
|
||||
|
@ -156,7 +156,8 @@ abstract class PlayerInjector {
|
||||
|
||||
// Retrieve the server handler
|
||||
if (serverHandlerField == null) {
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(
|
||||
"NetServerHandler", MinecraftReflection.getNetServerHandlerClass());
|
||||
proxyServerField = getProxyField(notchEntity, serverHandlerField);
|
||||
}
|
||||
|
||||
@ -166,7 +167,8 @@ abstract class PlayerInjector {
|
||||
|
||||
// Next, get the network manager
|
||||
if (networkManagerField == null)
|
||||
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
|
||||
networkManagerField = FuzzyReflection.fromObject(serverHandler).
|
||||
getFieldByType(".*" + MinecraftReflection.getNetworkManagerName());
|
||||
initializeNetworkManager(networkManagerField, serverHandler);
|
||||
}
|
||||
}
|
||||
@ -181,7 +183,8 @@ abstract class PlayerInjector {
|
||||
loginHandler = netLoginHandler;
|
||||
|
||||
if (netLoginNetworkField == null)
|
||||
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).getFieldByType(".*NetworkManager");
|
||||
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
|
||||
getFieldByType(".*" + MinecraftReflection.getNetworkManagerName());
|
||||
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
|
||||
}
|
||||
}
|
||||
@ -341,7 +344,7 @@ abstract class PlayerInjector {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
|
||||
|
||||
// It might be
|
||||
return reflection.getFieldByType(".*NetServerHandler");
|
||||
return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getNetServerHandlerClass());
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
// Damn
|
||||
@ -367,7 +370,7 @@ abstract class PlayerInjector {
|
||||
try {
|
||||
if (netHandlerField == null)
|
||||
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||
getFieldByType("net\\.minecraft\\.NetHandler");
|
||||
getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass());
|
||||
} catch (RuntimeException e1) {
|
||||
// Swallow it
|
||||
}
|
||||
@ -398,7 +401,8 @@ abstract class PlayerInjector {
|
||||
*/
|
||||
private Object getEntityPlayer(Object netHandler) throws IllegalAccessException {
|
||||
if (entityPlayerField == null)
|
||||
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(".*EntityPlayer");
|
||||
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(
|
||||
"EntityPlayer", MinecraftReflection.getEntityPlayerClass());
|
||||
return FieldUtils.readField(entityPlayerField, netHandler);
|
||||
}
|
||||
|
||||
|
@ -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.player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
@ -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.player;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -17,47 +17,88 @@
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Can copy an object field by field.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ObjectCloner {
|
||||
|
||||
public class ObjectWriter {
|
||||
// Cache structure modifiers
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static ConcurrentMap<Class, StructureModifier<Object>> cache =
|
||||
new ConcurrentHashMap<Class, StructureModifier<Object>>();
|
||||
|
||||
/**
|
||||
* Copy every field in object A to object B.
|
||||
* Retrieve a usable structure modifier for the given object type.
|
||||
* <p>
|
||||
* Will attempt to reuse any other structure modifiers we have cached.
|
||||
* @param type - the type of the object we are modifying.
|
||||
* @return A structure modifier for the given type.
|
||||
*/
|
||||
private StructureModifier<Object> getModifier(Class<?> type) {
|
||||
Class<?> packetClass = MinecraftReflection.getPacketClass();
|
||||
|
||||
// Handle subclasses of the packet class with our custom structure cache
|
||||
if (!type.equals(packetClass) && packetClass.isAssignableFrom(type)) {
|
||||
// Delegate to our already existing registry of structure modifiers
|
||||
return StructureCache.getStructure(type);
|
||||
}
|
||||
|
||||
StructureModifier<Object> modifier = cache.get(type);
|
||||
|
||||
// Create the structure modifier if we haven't already
|
||||
if (modifier == null) {
|
||||
StructureModifier<Object> value = new StructureModifier<Object>(type, null, false);
|
||||
modifier = cache.putIfAbsent(type, value);
|
||||
|
||||
if (modifier == null)
|
||||
modifier = value;
|
||||
}
|
||||
|
||||
// And we're done
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy every field in object A to object B. Each value is copied directly, and is not cloned.
|
||||
* <p>
|
||||
* The two objects must have the same number of fields of the same type.
|
||||
* @param source - fields to copy.
|
||||
* @param destination - fields to copy to.
|
||||
* @param commonType - type containing each field to copy.
|
||||
*/
|
||||
public static void copyTo(Object source, Object destination, Class<?> commonType) {
|
||||
public void copyTo(Object source, Object destination, Class<?> commonType) {
|
||||
// Note that we indicate that public fields will be copied the first time around
|
||||
copyToInternal(source, destination, commonType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for every non-static field that will be copied.
|
||||
* @param modifierSource - modifier for the original object.
|
||||
* @param modifierDest - modifier for the new cloned object.
|
||||
* @param fieldIndex - the current field index.
|
||||
*/
|
||||
protected void transformField(StructureModifier<Object> modifierSource, StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
Object value = modifierSource.read(fieldIndex);
|
||||
modifierDest.write(fieldIndex, value);
|
||||
}
|
||||
|
||||
// Internal method that will actually implement the recursion
|
||||
private void copyToInternal(Object source, Object destination, Class<?> commonType, boolean copyPublic) {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("Source cannot be NULL");
|
||||
if (destination == null)
|
||||
throw new IllegalArgumentException("Destination cannot be NULL");
|
||||
|
||||
StructureModifier<Object> modifier = cache.get(commonType);
|
||||
|
||||
// Create the structure modifier if we haven't already
|
||||
if (modifier == null) {
|
||||
StructureModifier<Object> value = new StructureModifier<Object>(commonType, null, false);
|
||||
modifier = cache.putIfAbsent(commonType, value);
|
||||
|
||||
if (modifier == null)
|
||||
modifier = value;
|
||||
}
|
||||
StructureModifier<Object> modifier = getModifier(commonType);
|
||||
|
||||
// Add target
|
||||
StructureModifier<Object> modifierSource = modifier.withTarget(source);
|
||||
@ -66,20 +107,20 @@ public class ObjectCloner {
|
||||
// Copy every field
|
||||
try {
|
||||
for (int i = 0; i < modifierSource.size(); i++) {
|
||||
if (!modifierDest.isReadOnly(i)) {
|
||||
Object value = modifierSource.read(i);
|
||||
modifierDest.write(i, value);
|
||||
}
|
||||
Field field = modifierSource.getField(i);
|
||||
int mod = field.getModifiers();
|
||||
|
||||
// System.out.println(String.format("Writing value %s to %s",
|
||||
// value, modifier.getFields().get(i).getName()));
|
||||
// Skip static fields. We also get the "public" fields fairly often, so we'll skip that.
|
||||
if (!Modifier.isStatic(mod) && (!Modifier.isPublic(mod) || copyPublic)) {
|
||||
transformField(modifierSource, modifierDest, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy private fields underneath
|
||||
Class<?> superclass = commonType.getSuperclass();
|
||||
|
||||
if (!superclass.equals(Object.class)) {
|
||||
copyTo(source, destination, superclass);
|
||||
copyToInternal(source, destination, superclass, false);
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
@ -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.reflect;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
@ -201,10 +201,16 @@ public class StructureModifier<TField> {
|
||||
* @return TRUE if the field by the given index is read-only, FALSE otherwise.
|
||||
*/
|
||||
public boolean isReadOnly(int fieldIndex) {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||
return Modifier.isFinal(getField(fieldIndex).getModifiers());
|
||||
}
|
||||
|
||||
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
|
||||
/**
|
||||
* Determine if a given field is public or not.
|
||||
* @param fieldIndex - field index.
|
||||
* @return TRUE if the field is public, FALSE otherwise.
|
||||
*/
|
||||
public boolean isPublic(int fieldIndex) {
|
||||
return Modifier.isPublic(getField(fieldIndex).getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -499,6 +505,19 @@ public class StructureModifier<TField> {
|
||||
return ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a field by index.
|
||||
* @param fieldIndex - index of the field to retrieve.
|
||||
* @return The field represented with the given index.
|
||||
* @throws IllegalArgumentException If no field with the given index can be found.
|
||||
*/
|
||||
public Field getField(int fieldIndex) {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||
|
||||
return data.get(fieldIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every value stored in the fields of the current type.
|
||||
* @return Every field value.
|
||||
@ -560,4 +579,6 @@ public class StructureModifier<TField> {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Implements a cloning procedure by trying multiple methods in turn until one is successful.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AggregateCloner implements Cloner {
|
||||
/**
|
||||
* Supplies the cloner factories with necessary parameters.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class BuilderParameters {
|
||||
// Can only be modified by the builder
|
||||
private InstanceProvider instanceProvider;
|
||||
private Cloner aggregateCloner;
|
||||
|
||||
// Used to construct the different types
|
||||
private InstanceProvider typeConstructor;
|
||||
|
||||
private BuilderParameters() {
|
||||
// Only allow inner classes to construct it.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the instance provider last set in the builder.
|
||||
* @return Current instance provider.
|
||||
*/
|
||||
public InstanceProvider getInstanceProvider() {
|
||||
return instanceProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the aggregate cloner that is being built.
|
||||
* @return The parent cloner.
|
||||
*/
|
||||
public Cloner getAggregateCloner() {
|
||||
return aggregateCloner;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for aggregate (combined) cloners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
private List<Function<BuilderParameters, Cloner>> factories = Lists.newArrayList();
|
||||
private BuilderParameters parameters;
|
||||
|
||||
/**
|
||||
* Create a new aggregate builder.
|
||||
*/
|
||||
public Builder() {
|
||||
this.parameters = new BuilderParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the instance provider supplied to all cloners in this builder.
|
||||
* @param provider - new instance provider.
|
||||
* @return The current builder.
|
||||
*/
|
||||
public Builder instanceProvider(InstanceProvider provider) {
|
||||
this.parameters.instanceProvider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the next cloner that will be considered in turn.
|
||||
* @param type - the type of the next cloner.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder andThen(final Class<? extends Cloner> type) {
|
||||
// Use reflection to generate a factory on the fly
|
||||
return andThen(new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
Object result = param.typeConstructor.create(type);
|
||||
|
||||
if (result == null) {
|
||||
throw new IllegalStateException("Constructed NULL instead of " + type);
|
||||
}
|
||||
|
||||
if (type.isAssignableFrom(result.getClass()))
|
||||
return (Cloner) result;
|
||||
else
|
||||
throw new IllegalStateException("Constructed " + result.getClass() + " instead of " + type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the next cloner that will be considered in turn.
|
||||
* @param factory - factory constructing the next cloner.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder andThen(Function<BuilderParameters, Cloner> factory) {
|
||||
factories.add(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new aggregate cloner using the supplied values.
|
||||
* @return A new aggregate cloner.
|
||||
*/
|
||||
public AggregateCloner build() {
|
||||
AggregateCloner newCloner = new AggregateCloner();
|
||||
|
||||
// The parameters we will pass to our cloners
|
||||
Cloner paramCloner = new NullableCloner(newCloner);
|
||||
InstanceProvider paramProvider = parameters.instanceProvider;
|
||||
|
||||
// Initialize parameters
|
||||
parameters.aggregateCloner = paramCloner;
|
||||
parameters.typeConstructor = DefaultInstances.fromArray(
|
||||
ExistingGenerator.fromObjectArray(new Object[] { paramCloner, paramProvider })
|
||||
);
|
||||
|
||||
// Build every cloner in the correct order
|
||||
List<Cloner> cloners = Lists.newArrayList();
|
||||
|
||||
for (int i = 0; i < factories.size(); i++) {
|
||||
Cloner cloner = factories.get(i).apply(parameters);
|
||||
|
||||
// See if we were successful
|
||||
if (cloner != null)
|
||||
cloners.add(cloner);
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot create cloner from %s (%s)", factories.get(i), i)
|
||||
);
|
||||
}
|
||||
|
||||
// We're done
|
||||
newCloner.setCloners(cloners);
|
||||
return newCloner;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a default aggregate cloner.
|
||||
*/
|
||||
public static final AggregateCloner DEFAULT = newBuilder().
|
||||
instanceProvider(DefaultInstances.DEFAULT).
|
||||
andThen(BukkitCloner.class).
|
||||
andThen(ImmutableDetector.class).
|
||||
andThen(CollectionCloner.class).
|
||||
andThen(FieldCloner.class).
|
||||
build();
|
||||
|
||||
// List of clone methods
|
||||
private List<Cloner> cloners;
|
||||
|
||||
private WeakReference<Object> lastObject;
|
||||
private int lastResult;
|
||||
|
||||
/**
|
||||
* Begins constructing a new aggregate cloner.
|
||||
* @return A builder for a new aggregate cloner.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new, empty aggregate cloner.
|
||||
*/
|
||||
private AggregateCloner() {
|
||||
// Only used by our builder above.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a view of the current list of cloners.
|
||||
* @return Current cloners.
|
||||
*/
|
||||
public List<Cloner> getCloners() {
|
||||
return Collections.unmodifiableList(cloners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cloners that will be used.
|
||||
* @param cloners - the cloners that will be used.
|
||||
*/
|
||||
private void setCloners(Iterable<? extends Cloner> cloners) {
|
||||
this.cloners = Lists.newArrayList(cloners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
// Optimize a bit
|
||||
lastResult = getFirstCloner(source);
|
||||
lastObject = new WeakReference<Object>(source);
|
||||
return lastResult >= 0 && lastResult < cloners.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the index of the first cloner capable of cloning the given object.
|
||||
* <p>
|
||||
* Returns an invalid index if no cloner is able to clone the object.
|
||||
* @param source - the object to clone.
|
||||
* @return The index of the cloner object.
|
||||
*/
|
||||
private int getFirstCloner(Object source) {
|
||||
for (int i = 0; i < cloners.size(); i++) {
|
||||
if (cloners.get(i).canClone(source))
|
||||
return i;
|
||||
}
|
||||
|
||||
return cloners.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source == null)
|
||||
throw new IllegalAccessError("source cannot be NULL.");
|
||||
int index = 0;
|
||||
|
||||
// Are we dealing with the same object?
|
||||
if (lastObject != null && lastObject.get() == source) {
|
||||
index = lastResult;
|
||||
} else {
|
||||
index = getFirstCloner(source);
|
||||
}
|
||||
|
||||
// Make sure the object is valid
|
||||
if (index < cloners.size()) {
|
||||
return cloners.get(index).clone(source);
|
||||
}
|
||||
|
||||
// Damn - failure
|
||||
throw new IllegalArgumentException("Cannot clone " + source + ": No cloner is sutable.");
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
|
||||
/**
|
||||
* Represents an object that can clone a specific list of Bukkit- and Minecraft-related objects.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitCloner implements Cloner {
|
||||
// List of classes we support
|
||||
private Class<?>[] clonableClasses = { MinecraftReflection.getItemStackClass(), MinecraftReflection.getChunkPositionClass(),
|
||||
MinecraftReflection.getDataWatcherClass() };
|
||||
|
||||
private int findMatchingClass(Class<?> type) {
|
||||
// See if is a subclass of any of our supported superclasses
|
||||
for (int i = 0; i < clonableClasses.length; i++) {
|
||||
if (clonableClasses[i].isAssignableFrom(type))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
return findMatchingClass(source.getClass()) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("source cannot be NULL.");
|
||||
|
||||
// Convert to a wrapper
|
||||
switch (findMatchingClass(source.getClass())) {
|
||||
case 0:
|
||||
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone());
|
||||
case 1:
|
||||
EquivalentConverter<ChunkPosition> chunkConverter = ChunkPosition.getConverter();
|
||||
return chunkConverter.getGeneric(clonableClasses[1], chunkConverter.getSpecific(source));
|
||||
case 2:
|
||||
EquivalentConverter<WrappedDataWatcher> dataConverter = BukkitConverters.getDataWatcherConverter();
|
||||
return dataConverter.getGeneric(clonableClasses[2], dataConverter.getSpecific(source).deepClone());
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot clone objects of type " + source.getClass());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
/**
|
||||
* Represents an object that is capable of cloning other objects.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface Cloner {
|
||||
/**
|
||||
* Determine whether or not the current cloner can clone the given object.
|
||||
* @param source - the object that is being considered.
|
||||
* @return TRUE if this cloner can actually clone the given object, FALSE otherwise.
|
||||
*/
|
||||
public boolean canClone(Object source);
|
||||
|
||||
/**
|
||||
* Perform the clone.
|
||||
* <p>
|
||||
* This method should never be called unless a corresponding {@link #canClone(Object)} returns TRUE.
|
||||
* @param source - the value to clone.
|
||||
* @return A cloned value.
|
||||
* @throws IllegalArgumentException If this cloner cannot perform the clone.
|
||||
*/
|
||||
public Object clone(Object source);
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Attempts to clone collection and array classes.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CollectionCloner implements Cloner {
|
||||
private final Cloner defaultCloner;
|
||||
|
||||
/**
|
||||
* Constructs a new collection and array cloner with the given inner element cloner.
|
||||
* @param defaultCloner - default inner element cloner.
|
||||
*/
|
||||
public CollectionCloner(Cloner defaultCloner) {
|
||||
this.defaultCloner = defaultCloner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
Class<?> clazz = source.getClass();
|
||||
return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || clazz.isArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("source cannot be NULL.");
|
||||
|
||||
Class<?> clazz = source.getClass();
|
||||
|
||||
if (source instanceof Collection) {
|
||||
Collection<Object> copy = cloneConstructor(Collection.class, clazz, source);
|
||||
|
||||
// Next, clone each element in the collection
|
||||
copy.clear();
|
||||
|
||||
for (Object element : (Collection<Object>) source) {
|
||||
copy.add(getClone(element, source));
|
||||
}
|
||||
|
||||
return copy;
|
||||
|
||||
} else if (source instanceof Map) {
|
||||
|
||||
Map<Object, Object> copy = cloneConstructor(Map.class, clazz, source);
|
||||
|
||||
// Next, clone each element in the collection
|
||||
copy.clear();
|
||||
|
||||
for (Entry<Object, Object> element : ((Map<Object, Object>) source).entrySet()) {
|
||||
Object key = getClone(element.getKey(), source);
|
||||
Object value = getClone(element.getValue(), source);
|
||||
copy.put(key, value);
|
||||
}
|
||||
|
||||
return copy;
|
||||
|
||||
} else if (clazz.isArray()) {
|
||||
// Get the length
|
||||
int lenght = Array.getLength(source);
|
||||
Class<?> component = clazz.getComponentType();
|
||||
|
||||
// Can we speed things up by making a shallow copy instead?
|
||||
if (ImmutableDetector.isImmutable(component)) {
|
||||
return clonePrimitive(component, source);
|
||||
}
|
||||
|
||||
// Create a new copy
|
||||
Object copy = Array.newInstance(clazz.getComponentType(), lenght);
|
||||
|
||||
// Set each element
|
||||
for (int i = 0; i < lenght; i++) {
|
||||
Object element = Array.get(source, i);
|
||||
|
||||
if (defaultCloner.canClone(element))
|
||||
Array.set(copy, i, defaultCloner.clone(element));
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot clone " + element + " in array " + source);
|
||||
}
|
||||
|
||||
// And we're done
|
||||
return copy;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(source + " is not an array nor a Collection.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an element using the default cloner.
|
||||
* @param element - the element to clone.
|
||||
* @param container - where the element is stored.
|
||||
* @return The cloned element.
|
||||
*/
|
||||
private Object getClone(Object element, Object container) {
|
||||
if (defaultCloner.canClone(element))
|
||||
return defaultCloner.clone(element);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot clone " + element + " in container " + container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a primitive or immutable array by calling its clone method.
|
||||
* @param component - the component type of the array.
|
||||
* @param source - the array itself.
|
||||
* @return The cloned array.
|
||||
*/
|
||||
private Object clonePrimitive(Class<?> component, Object source) {
|
||||
// Cast and call the correct version
|
||||
if (byte.class.equals(component))
|
||||
return ((byte[]) source).clone();
|
||||
else if (short.class.equals(component))
|
||||
return ((short[]) source).clone();
|
||||
else if (int.class.equals(component))
|
||||
return ((int[]) source).clone();
|
||||
else if (long.class.equals(component))
|
||||
return ((long[]) source).clone();
|
||||
else if (float.class.equals(component))
|
||||
return ((float[]) source).clone();
|
||||
else if (double.class.equals(component))
|
||||
return ((double[]) source).clone();
|
||||
else if (char.class.equals(component))
|
||||
return ((char[]) source).clone();
|
||||
else if (boolean.class.equals(component))
|
||||
return ((boolean[]) source).clone();
|
||||
else
|
||||
return ((Object[]) source).clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an object by calling its clone constructor, or alternatively, a "clone" method.
|
||||
* @param superclass - the superclass we expect in the clone constructor.
|
||||
* @param clazz - the class of the object.
|
||||
* @param source - the object itself.
|
||||
* @return A cloned object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T cloneConstructor(Class<?> superclass, Class<?> clazz, Object source) {
|
||||
|
||||
// Not all collections or maps implement "clone", but most *do* implement the "copy constructor" pattern
|
||||
try {
|
||||
Constructor<?> constructCopy = clazz.getConstructor(Collection.class);
|
||||
return (T) constructCopy.newInstance(source);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return (T) cloneObject(clazz, source);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct collection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an object by calling "clone" using reflection.
|
||||
* @param clazz - the class type.
|
||||
* @param obj - the object to clone.
|
||||
* @return The cloned object.
|
||||
*/
|
||||
private Object cloneObject(Class<?> clazz, Object source) {
|
||||
// Try to clone it instead
|
||||
try {
|
||||
return clazz.getMethod("clone").invoke(source);
|
||||
} catch (Exception e1) {
|
||||
throw new RuntimeException("Cannot copy " + source + " (" + clazz + ")", e1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default cloner used to clone the content of each element in the collection.
|
||||
* @return Cloner used to clone elements.
|
||||
*/
|
||||
public Cloner getDefaultCloner() {
|
||||
return defaultCloner;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||
|
||||
/**
|
||||
* Represents a class capable of cloning objects by deeply copying its fields.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class FieldCloner implements Cloner {
|
||||
protected Cloner defaultCloner;
|
||||
protected InstanceProvider instanceProvider;
|
||||
|
||||
// Used to clone objects
|
||||
protected ObjectWriter writer;
|
||||
|
||||
/**
|
||||
* Constructs a field cloner that copies objects by reading and writing the internal fields directly.
|
||||
* @param defaultCloner - the default cloner used while copying fields.
|
||||
* @param instanceProvider - used to construct new, empty copies of a given type.
|
||||
*/
|
||||
public FieldCloner(Cloner defaultCloner, InstanceProvider instanceProvider) {
|
||||
this.defaultCloner = defaultCloner;
|
||||
this.instanceProvider = instanceProvider;
|
||||
|
||||
// Remember to clone the value too
|
||||
this.writer = new ObjectWriter() {
|
||||
@Override
|
||||
protected void transformField(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
defaultTransform(modifierDest, modifierDest, getDefaultCloner(), fieldIndex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the field transform. Applies a clone operation before a field value is written.
|
||||
* @param modifierSource - modifier for the original object.
|
||||
* @param modifierDest - modifier for the new cloned object.
|
||||
* @param defaultCloner - cloner to use.
|
||||
* @param fieldIndex - the current field index.
|
||||
*/
|
||||
protected void defaultTransform(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, Cloner defaultCloner, int fieldIndex) {
|
||||
|
||||
Object value = modifierSource.read(fieldIndex);
|
||||
modifierDest.write(fieldIndex, defaultCloner.clone(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
// Attempt to create the type
|
||||
return instanceProvider.create(source.getClass()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source == null)
|
||||
throw new IllegalArgumentException("source cannot be NULL.");
|
||||
|
||||
Object copy = instanceProvider.create(source.getClass());
|
||||
|
||||
// Copy public and private fields alike. Skip static fields.
|
||||
writer.copyTo(source, copy, source.getClass());
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default cloner used to clone the content of each field.
|
||||
* @return Cloner used to clone fields.
|
||||
*/
|
||||
public Cloner getDefaultCloner() {
|
||||
return defaultCloner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the instance provider this cloner is using to create new, empty classes.
|
||||
* @return The instance provider in use.
|
||||
*/
|
||||
public InstanceProvider getInstanceProvider() {
|
||||
return instanceProvider;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
/**
|
||||
* Detects classes that are immutable, and thus doesn't require cloning.
|
||||
* <p>
|
||||
* This ought to have no false positives, but plenty of false negatives.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ImmutableDetector implements Cloner {
|
||||
// Notable immutable classes we might encounter
|
||||
private static final Class<?>[] immutableClasses = {
|
||||
StackTraceElement.class, BigDecimal.class,
|
||||
BigInteger.class, Locale.class, UUID.class,
|
||||
URL.class, URI.class, Inet4Address.class,
|
||||
Inet6Address.class, InetSocketAddress.class,
|
||||
SecretKey.class, PublicKey.class
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
// Don't accept NULL
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
return isImmutable(source.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given type is probably immutable.
|
||||
* @param type - the type to check.
|
||||
* @return TRUE if the type is immutable, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isImmutable(Class<?> type) {
|
||||
// Cases that are definitely not true
|
||||
if (type.isArray())
|
||||
return false;
|
||||
|
||||
// All primitive types
|
||||
if (Primitives.isWrapperType(type) || String.class.equals(type))
|
||||
return true;
|
||||
// May not be true, but if so, that kind of code is broken anyways
|
||||
if (type.isEnum())
|
||||
return true;
|
||||
|
||||
for (Class<?> clazz : immutableClasses)
|
||||
if (clazz.equals(type))
|
||||
return true;
|
||||
|
||||
// Probably not
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
// Safe if the class is immutable
|
||||
return source;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.reflect.cloning;
|
||||
|
||||
/**
|
||||
* Creates a cloner wrapper that accepts and clones NULL values.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class NullableCloner implements Cloner {
|
||||
protected Cloner wrapped;
|
||||
|
||||
public NullableCloner(Cloner wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
// Don't pass the NULL value to the cloner
|
||||
if (source == null)
|
||||
return null;
|
||||
else
|
||||
return wrapped.clone(source);
|
||||
}
|
||||
|
||||
public Cloner getWrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.instances;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
@ -30,7 +32,7 @@ import com.google.common.collect.ImmutableList;
|
||||
* @author Kristian
|
||||
*
|
||||
*/
|
||||
public class DefaultInstances {
|
||||
public class DefaultInstances implements InstanceProvider {
|
||||
|
||||
/**
|
||||
* Standard default instance provider.
|
||||
@ -326,4 +328,9 @@ public class DefaultInstances {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
return getDefault(type);
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,16 @@
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Provides instance constructors using a list of existing values.
|
||||
@ -33,8 +36,52 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ExistingGenerator implements InstanceProvider {
|
||||
/**
|
||||
* Represents a single node in the tree of possible values.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private static final class Node {
|
||||
private Map<Class<?>, Node> children;
|
||||
private Class<?> key;
|
||||
private Object value;
|
||||
private int level;
|
||||
|
||||
private Map<String, Object> existingValues = new HashMap<String, Object>();
|
||||
public Node(Class<?> key, Object value, int level) {
|
||||
this.children = new HashMap<Class<?>, Node>();
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public Node addChild(Node node) {
|
||||
children.put(node.key, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public Collection<Node> getChildren() {
|
||||
return children.values();
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Node getChild(Class<?> clazz) {
|
||||
return children.get(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
// Represents the root node
|
||||
private Node root = new Node(null, null, 0);
|
||||
|
||||
private ExistingGenerator() {
|
||||
// Only accessible to the constructors
|
||||
@ -110,18 +157,94 @@ public class ExistingGenerator implements InstanceProvider {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||
|
||||
existingValues.put(value.getClass().getName(), value);
|
||||
addObject(value.getClass(), value);
|
||||
}
|
||||
|
||||
private void addObject(Class<?> type, Object value) {
|
||||
existingValues.put(type.getName(), value);
|
||||
Node node = getLeafNode(root, type, false);
|
||||
|
||||
// Set the value
|
||||
node.setValue(value);
|
||||
}
|
||||
|
||||
private Node getLeafNode(final Node start, Class<?> type, boolean readOnly) {
|
||||
Class<?>[] path = getHierachy(type);
|
||||
Node current = start;
|
||||
|
||||
for (int i = 0; i < path.length; i++) {
|
||||
Node next = getNext(current, path[i], readOnly);
|
||||
|
||||
// Try every interface too
|
||||
if (next == null && readOnly) {
|
||||
current = null;
|
||||
break;
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
|
||||
// And we're done
|
||||
return current;
|
||||
}
|
||||
|
||||
private Node getNext(Node current, Class<?> clazz, boolean readOnly) {
|
||||
Node next = current.getChild(clazz);
|
||||
|
||||
// Add a new node if needed
|
||||
if (next == null && !readOnly) {
|
||||
next = current.addChild(new Node(clazz, null, current.getLevel() + 1));
|
||||
}
|
||||
|
||||
// Add interfaces
|
||||
if (next != null && !readOnly && !clazz.isInterface()) {
|
||||
for (Class<?> clazzInterface : clazz.getInterfaces()) {
|
||||
getLeafNode(root, clazzInterface, readOnly).addChild(next);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private Node getLowestLeaf(Node current) {
|
||||
Node candidate = current;
|
||||
|
||||
// Depth-first search
|
||||
for (Node child : current.getChildren()) {
|
||||
Node subtree = getLowestLeaf(child);
|
||||
|
||||
// Get the lowest node
|
||||
if (subtree.getValue() != null && candidate.getLevel() < subtree.getLevel()) {
|
||||
candidate = subtree;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private Class<?>[] getHierachy(Class<?> type) {
|
||||
LinkedList<Class<?>> levels = Lists.newLinkedList();
|
||||
|
||||
// Add each class from the hierachy
|
||||
for (; type != null; type = type.getSuperclass()) {
|
||||
levels.addFirst(type);
|
||||
}
|
||||
|
||||
return levels.toArray(new Class<?>[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
Object value = existingValues.get(type.getName());
|
||||
// Locate the type in the hierachy
|
||||
Node node = getLeafNode(root, type, true);
|
||||
|
||||
// Next, get the lowest leaf node
|
||||
if (node != null) {
|
||||
node = getLowestLeaf(node);
|
||||
}
|
||||
|
||||
// NULL values indicate that the generator failed
|
||||
return value;
|
||||
if (node != null)
|
||||
return node.getValue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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.utility;
|
||||
|
||||
import java.util.Map;
|
||||
@ -18,16 +35,24 @@ class CachedPackage {
|
||||
this.cache = Maps.newConcurrentMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given class with a class name.
|
||||
* @param className - class name.
|
||||
* @param clazz - type of class.
|
||||
*/
|
||||
public void setPackageClass(String className, Class<?> clazz) {
|
||||
cache.put(className, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class object of a specific class in the current package.
|
||||
* @param className - the specific class.
|
||||
* @return Class object.
|
||||
* @throws RuntimeException If we are unable to find the given class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Class getPackageClass(String className) {
|
||||
public Class<?> getPackageClass(String className) {
|
||||
try {
|
||||
Class result = cache.get(className);
|
||||
Class<?> result = cache.get(className);
|
||||
|
||||
// Concurrency is not a problem - we don't care if we look up a class twice
|
||||
if (result == null) {
|
||||
|
@ -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.utility;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
@ -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.utility;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
@ -11,6 +28,7 @@ import org.bukkit.Server;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* Methods and constants specifically used in conjuction with reflecting Minecraft object.
|
||||
@ -35,10 +53,14 @@ public class MinecraftReflection {
|
||||
private static CachedPackage craftbukkitPackage;
|
||||
|
||||
// org.bukkit.craftbukkit
|
||||
private static Class<?> craftItemStackClass;
|
||||
private static Constructor<?> craftNMSConstructor;
|
||||
private static Constructor<?> craftBukkitConstructor;
|
||||
|
||||
// New in 1.4.5
|
||||
private static Method craftNMSMethod;
|
||||
private static Method craftBukkitMethod;
|
||||
private static boolean craftItemStackFailed;
|
||||
|
||||
// net.minecraft.server
|
||||
private static Class<?> itemStackArrayClass;
|
||||
|
||||
@ -107,6 +129,24 @@ public class MinecraftReflection {
|
||||
return fullName.substring(0, fullName.lastIndexOf("."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically retrieve the Bukkit entity from a given entity.
|
||||
* @param nmsObject - the NMS entity.
|
||||
* @return A bukkit entity.
|
||||
* @throws RuntimeException If we were unable to retrieve the Bukkit entity.
|
||||
*/
|
||||
public static Object getBukkitEntity(Object nmsObject) {
|
||||
if (nmsObject == null)
|
||||
return null;
|
||||
|
||||
// We will have to do this dynamically, unfortunately
|
||||
try {
|
||||
return nmsObject.getClass().getMethod("getBukkitEntity").invoke(nmsObject);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot get Bukkit entity from " + nmsObject, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given object can be found within the package net.minecraft.server.
|
||||
* @param obj - the object to test.
|
||||
@ -134,30 +174,11 @@ public class MinecraftReflection {
|
||||
return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically retrieve the Bukkit entity from a given entity.
|
||||
* @param nmsObject - the NMS entity.
|
||||
* @return A bukkit entity.
|
||||
* @throws RuntimeException If we were unable to retrieve the Bukkit entity.
|
||||
*/
|
||||
public static Object getBukkitEntity(Object nmsObject) {
|
||||
if (nmsObject == null)
|
||||
return null;
|
||||
|
||||
// We will have to do this dynamically, unfortunately
|
||||
try {
|
||||
return nmsObject.getClass().getMethod("getBukkitEntity").invoke(nmsObject);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot get Bukkit entity from " + nmsObject, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given object is a ChunkPosition.
|
||||
* @param obj - the object to test.
|
||||
* @return TRUE if it can, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isChunkPosition(Object obj) {
|
||||
return getChunkPositionClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -167,7 +188,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the object to test.
|
||||
* @return TRUE if it can, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isChunkCoordinates(Object obj) {
|
||||
return getChunkCoordinatesClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -177,7 +197,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isPacketClass(Object obj) {
|
||||
return getPacketClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -187,7 +206,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isLoginHandler(Object obj) {
|
||||
return getNetLoginHandlerClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -197,7 +215,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isMinecraftEntity(Object obj) {
|
||||
return getEntityClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -207,7 +224,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isItemStack(Object value) {
|
||||
return getItemStackClass().isAssignableFrom(value.getClass());
|
||||
}
|
||||
@ -217,7 +233,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isMinecraftPlayer(Object obj) {
|
||||
return getEntityPlayerClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -227,7 +242,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isWatchableObject(Object obj) {
|
||||
return getWatchableObjectClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -237,7 +251,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isDataWatcher(Object obj) {
|
||||
return getDataWatcherClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -247,7 +260,6 @@ public class MinecraftReflection {
|
||||
* @param obj - the given object.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isCraftItemStack(Object obj) {
|
||||
return getCraftItemStackClass().isAssignableFrom(obj.getClass());
|
||||
}
|
||||
@ -256,8 +268,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the EntityPlayer (NMS) class.
|
||||
* @return The entity class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getEntityPlayerClass() {
|
||||
public static Class<?> getEntityPlayerClass() {
|
||||
return getMinecraftClass("EntityPlayer");
|
||||
}
|
||||
|
||||
@ -265,8 +276,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the entity (NMS) class.
|
||||
* @return The entity class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getEntityClass() {
|
||||
public static Class<?> getEntityClass() {
|
||||
return getMinecraftClass("Entity");
|
||||
}
|
||||
|
||||
@ -274,8 +284,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the packet class.
|
||||
* @return The packet class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getPacketClass() {
|
||||
public static Class<?> getPacketClass() {
|
||||
return getMinecraftClass("Packet");
|
||||
}
|
||||
|
||||
@ -283,17 +292,39 @@ public class MinecraftReflection {
|
||||
* Retrieve the NetLoginHandler class.
|
||||
* @return The NetLoginHandler class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getNetLoginHandlerClass() {
|
||||
return getMinecraftClass("NetLoginHandler");
|
||||
public static Class<?> getNetLoginHandlerClass() {
|
||||
return getMinecraftClass("NetLoginHandler", "PendingConnection");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NetLoginHandler class.
|
||||
* @return The NetLoginHandler class.
|
||||
* Retrieve the NetServerHandler class.
|
||||
* @return The NetServerHandler class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getItemStackClass() {
|
||||
public static Class<?> getNetServerHandlerClass() {
|
||||
return getMinecraftClass("NetServerHandler", "PlayerConnection");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NetworkManager class.
|
||||
* @return The NetworkManager class.
|
||||
*/
|
||||
public static Class<?> getNetworkManagerClass() {
|
||||
return getMinecraftClass("NetworkManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NetHandler class.
|
||||
* @return The NetHandler class.
|
||||
*/
|
||||
public static Class<?> getNetHandlerClass() {
|
||||
return getMinecraftClass("NetHandler", "Connection");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NMS ItemStack class.
|
||||
* @return The ItemStack class.
|
||||
*/
|
||||
public static Class<?> getItemStackClass() {
|
||||
return getMinecraftClass("ItemStack");
|
||||
}
|
||||
|
||||
@ -301,17 +332,23 @@ public class MinecraftReflection {
|
||||
* Retrieve the WorldType class.
|
||||
* @return The WorldType class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getWorldTypeClass() {
|
||||
public static Class<?> getWorldTypeClass() {
|
||||
return getMinecraftClass("WorldType");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the MinecraftServer class.
|
||||
* @return MinecraftServer class.
|
||||
*/
|
||||
public static Class<?> getMinecraftServerClass() {
|
||||
return getMinecraftClass("MinecraftServer");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the DataWatcher class.
|
||||
* @return The DataWatcher class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getDataWatcherClass() {
|
||||
public static Class<?> getDataWatcherClass() {
|
||||
return getMinecraftClass("DataWatcher");
|
||||
}
|
||||
|
||||
@ -319,8 +356,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the ChunkPosition class.
|
||||
* @return The ChunkPosition class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getChunkPositionClass() {
|
||||
public static Class<?> getChunkPositionClass() {
|
||||
return getMinecraftClass("ChunkPosition");
|
||||
}
|
||||
|
||||
@ -328,8 +364,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the ChunkPosition class.
|
||||
* @return The ChunkPosition class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getChunkCoordinatesClass() {
|
||||
public static Class<?> getChunkCoordinatesClass() {
|
||||
return getMinecraftClass("ChunkCoordinates");
|
||||
}
|
||||
|
||||
@ -337,8 +372,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the WatchableObject class.
|
||||
* @return The WatchableObject class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getWatchableObjectClass() {
|
||||
public static Class<?> getWatchableObjectClass() {
|
||||
return getMinecraftClass("WatchableObject");
|
||||
}
|
||||
|
||||
@ -346,8 +380,7 @@ public class MinecraftReflection {
|
||||
* Retrieve the ItemStack[] class.
|
||||
* @return The ItemStack[] class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getItemStackArrayClass() {
|
||||
public static Class<?> getItemStackArrayClass() {
|
||||
if (itemStackArrayClass == null)
|
||||
itemStackArrayClass = getArrayClass(getItemStackClass());
|
||||
return itemStackArrayClass;
|
||||
@ -358,8 +391,7 @@ public class MinecraftReflection {
|
||||
* @param componentType - type of each element in the array.
|
||||
* @return The class of the array.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getArrayClass(Class componentType) {
|
||||
public static Class<?> getArrayClass(Class<?> componentType) {
|
||||
// Bit of a hack, but it works
|
||||
return Array.newInstance(componentType, 0).getClass();
|
||||
}
|
||||
@ -368,11 +400,8 @@ public class MinecraftReflection {
|
||||
* Retrieve the CraftItemStack class.
|
||||
* @return The CraftItemStack class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getCraftItemStackClass() {
|
||||
if (craftItemStackClass == null)
|
||||
craftItemStackClass = getCraftBukkitClass("inventory.CraftItemStack");
|
||||
return craftItemStackClass;
|
||||
public static Class<?> getCraftItemStackClass() {
|
||||
return getCraftBukkitClass("inventory.CraftItemStack");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -380,12 +409,19 @@ public class MinecraftReflection {
|
||||
* @param bukkitItemStack - the Bukkit ItemStack to convert.
|
||||
* @return A CraftItemStack as an ItemStack.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ItemStack getBukkitItemStack(ItemStack bukkitItemStack) {
|
||||
// Delegate this task to the method that can execute it
|
||||
if (craftBukkitMethod != null)
|
||||
return getBukkitItemByMethod(bukkitItemStack);
|
||||
|
||||
if (craftBukkitConstructor == null) {
|
||||
try {
|
||||
craftBukkitConstructor = getCraftItemStackClass().getConstructor(ItemStack.class);
|
||||
} catch (Exception e) {
|
||||
// See if this method works
|
||||
if (!craftItemStackFailed)
|
||||
return getBukkitItemByMethod(bukkitItemStack);
|
||||
|
||||
throw new RuntimeException("Cannot find CraftItemStack(org.bukkit.inventory.ItemStack).", e);
|
||||
}
|
||||
}
|
||||
@ -398,17 +434,42 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemStack getBukkitItemByMethod(ItemStack bukkitItemStack) {
|
||||
if (craftBukkitMethod == null) {
|
||||
try {
|
||||
craftBukkitMethod = getCraftItemStackClass().getMethod("asCraftCopy", ItemStack.class);
|
||||
} catch (Exception e) {
|
||||
craftItemStackFailed = true;
|
||||
throw new RuntimeException("Cannot find CraftItemStack.asCraftCopy(org.bukkit.inventory.ItemStack).", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, construct it
|
||||
try {
|
||||
return (ItemStack) craftBukkitMethod.invoke(null, bukkitItemStack);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct CraftItemStack.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Bukkit ItemStack from a given net.minecraft.server ItemStack.
|
||||
* @param minecraftItemStack - the NMS ItemStack to wrap.
|
||||
* @return The wrapped ItemStack.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ItemStack getBukkitItemStack(Object minecraftItemStack) {
|
||||
// Delegate this task to the method that can execute it
|
||||
if (craftNMSMethod != null)
|
||||
return getBukkitItemByMethod(minecraftItemStack);
|
||||
|
||||
if (craftNMSConstructor == null) {
|
||||
try {
|
||||
craftNMSConstructor = getCraftItemStackClass().getConstructor(minecraftItemStack.getClass());
|
||||
} catch (Exception e) {
|
||||
// Give it a try
|
||||
if (!craftItemStackFailed)
|
||||
return getBukkitItemByMethod(minecraftItemStack);
|
||||
|
||||
throw new RuntimeException("Cannot find CraftItemStack(net.mineraft.server.ItemStack).", e);
|
||||
}
|
||||
}
|
||||
@ -421,6 +482,24 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemStack getBukkitItemByMethod(Object minecraftItemStack) {
|
||||
if (craftNMSMethod == null) {
|
||||
try {
|
||||
craftNMSMethod = getCraftItemStackClass().getMethod("asCraftMirror", minecraftItemStack.getClass());
|
||||
} catch (Exception e) {
|
||||
craftItemStackFailed = true;
|
||||
throw new RuntimeException("Cannot find CraftItemStack.asCraftMirror(net.mineraft.server.ItemStack).", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, construct it
|
||||
try {
|
||||
return (ItemStack) craftNMSMethod.invoke(null, minecraftItemStack);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot construct CraftItemStack.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the net.minecraft.server ItemStack from a Bukkit ItemStack.
|
||||
* @param stack - the Bukkit ItemStack to convert.
|
||||
@ -454,10 +533,63 @@ public class MinecraftReflection {
|
||||
* @return Class object.
|
||||
* @throws RuntimeException If we are unable to find the given class.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getMinecraftClass(String className) {
|
||||
public static Class<?> getMinecraftClass(String className) {
|
||||
if (minecraftPackage == null)
|
||||
minecraftPackage = new CachedPackage(getMinecraftPackage());
|
||||
return minecraftPackage.getPackageClass(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the first class that matches a specified Minecraft name.
|
||||
* @param classes - the specific Minecraft class.
|
||||
* @return Class object.
|
||||
* @throws RuntimeException If we are unable to find any of the given classes.
|
||||
*/
|
||||
public static Class<?> getMinecraftClass(String className, String... aliases) {
|
||||
try {
|
||||
// Try the main class first
|
||||
return getMinecraftClass(className);
|
||||
} catch (RuntimeException e1) {
|
||||
Class<?> success = null;
|
||||
|
||||
// Try every alias too
|
||||
for (String alias : aliases) {
|
||||
try {
|
||||
success = getMinecraftClass(alias);
|
||||
break;
|
||||
} catch (RuntimeException e2) {
|
||||
// Swallov
|
||||
}
|
||||
}
|
||||
|
||||
if (success != null) {
|
||||
// Save it for later
|
||||
minecraftPackage.setPackageClass(className, success);
|
||||
return success;
|
||||
} else {
|
||||
// Hack failed
|
||||
throw new RuntimeException(
|
||||
String.format("Unable to find %s (%s)",
|
||||
className,
|
||||
Joiner.on(", ").join(aliases))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically retrieve the NetworkManager name.
|
||||
* @return Name of the NetworkManager class.
|
||||
*/
|
||||
public static String getNetworkManagerName() {
|
||||
return getNetworkManagerClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically retrieve the name of the current NetLoginHandler.
|
||||
* @return Name of the NetLoginHandler class.
|
||||
*/
|
||||
public static String getNetLoginHandlerName() {
|
||||
return getNetLoginHandlerClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
@ -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.wrappers;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
@ -155,7 +172,6 @@ public class BukkitConverters {
|
||||
return null;
|
||||
|
||||
return getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WorldType specific) {
|
||||
try {
|
||||
@ -170,7 +186,6 @@ public class BukkitConverters {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
try {
|
||||
|
@ -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.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -133,7 +150,6 @@ public class ChunkPosition {
|
||||
*/
|
||||
public static EquivalentConverter<ChunkPosition> getConverter() {
|
||||
return new EquivalentConverter<ChunkPosition>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
|
||||
if (chunkPositionConstructor == null) {
|
||||
@ -166,7 +182,8 @@ public class ChunkPosition {
|
||||
|
||||
if (intModifier.size() >= 3) {
|
||||
try {
|
||||
return new ChunkPosition(intModifier.read(0), intModifier.read(1), intModifier.read(2));
|
||||
StructureModifier<Integer> instance = intModifier.withTarget(generic);
|
||||
return new ChunkPosition(instance.read(0), instance.read(1), instance.read(2));
|
||||
} catch (FieldAccessException e) {
|
||||
// This is an exeptional work-around, so we don't want to burden the caller with the messy details
|
||||
throw new RuntimeException("Field access error.", e);
|
||||
|
@ -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.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
@ -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.wrappers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -275,6 +292,37 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick checks
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (obj instanceof WrappedDataWatcher) {
|
||||
WrappedDataWatcher other = (WrappedDataWatcher) obj;
|
||||
Iterator<WrappedWatchableObject> first = iterator(), second = other.iterator();
|
||||
|
||||
// Make sure they're the same size
|
||||
if (size() != other.size())
|
||||
return false;
|
||||
|
||||
for (; first.hasNext() && second.hasNext(); ) {
|
||||
// See if the two elements are equal
|
||||
if (!first.next().equals(second.next()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getWatchableObjects().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of every index associated with a watched object.
|
||||
* @return Every watched object index.
|
||||
|
@ -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.wrappers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@ -49,7 +66,6 @@ public class WrappedWatchableObject {
|
||||
* @param index - the index.
|
||||
* @param value - non-null value of specific types.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public WrappedWatchableObject(int index, Object value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||
@ -296,8 +312,8 @@ public class WrappedWatchableObject {
|
||||
* @throws FieldAccessException If we're unable to use reflection.
|
||||
*/
|
||||
public WrappedWatchableObject deepClone() throws FieldAccessException {
|
||||
@SuppressWarnings("unchecked")
|
||||
WrappedWatchableObject clone = new WrappedWatchableObject(DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass()));
|
||||
WrappedWatchableObject clone = new WrappedWatchableObject(
|
||||
DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass()));
|
||||
|
||||
clone.setDirtyState(getDirtyState());
|
||||
clone.setIndex(getIndex());
|
||||
|
@ -2,7 +2,7 @@ global:
|
||||
# Settings for the automatic version updater
|
||||
auto updater:
|
||||
notify: true
|
||||
download: true
|
||||
download: false
|
||||
|
||||
# Number of seconds to wait until a new update is downloaded
|
||||
delay: 43200 # 12 hours
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: ProtocolLib
|
||||
version: 1.8.0
|
||||
version: 1.9.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
@ -0,0 +1,382 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.List;
|
||||
|
||||
// Will have to be updated for every version though
|
||||
import org.bukkit.craftbukkit.v1_4_6.inventory.CraftItemFactory;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.inventory.ItemFactory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
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.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
// Ensure that the CraftItemFactory is mockable
|
||||
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
|
||||
@PrepareForTest(CraftItemFactory.class)
|
||||
public class PacketContainerTest {
|
||||
// Helper converters
|
||||
private EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter();
|
||||
private EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter();
|
||||
|
||||
@BeforeClass
|
||||
public static void initializeBukkit() throws IllegalAccessException {
|
||||
// Initialize reflection
|
||||
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6");
|
||||
|
||||
// Mock the server object
|
||||
Server mockedServer = mock(Server.class);
|
||||
ItemFactory mockedFactory = mock(CraftItemFactory.class);
|
||||
ItemMeta mockedMeta = mock(ItemMeta.class);
|
||||
|
||||
when(mockedServer.getItemFactory()).thenReturn(mockedFactory);
|
||||
when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta);
|
||||
|
||||
// Inject this fake server
|
||||
FieldUtils.writeStaticField(Bukkit.class, "server", mockedServer, true);
|
||||
|
||||
// And the fake item factory
|
||||
FieldUtils.writeStaticField(CraftItemFactory.class, "instance", mockedFactory, true);
|
||||
}
|
||||
|
||||
private <T> void testPrimitive(StructureModifier<T> modifier, int index, T initialValue, T testValue) {
|
||||
// Check initial value
|
||||
assertEquals(initialValue, modifier.read(index));
|
||||
|
||||
// Test assignment
|
||||
modifier.write(index, testValue);
|
||||
assertEquals(testValue, modifier.read(0));
|
||||
}
|
||||
|
||||
private <T> void testObjectArray(StructureModifier<T[]> modifier, int index, T[] initialValue, T[] testValue) {
|
||||
// Check initial value
|
||||
assertNull(modifier.read(index));
|
||||
modifier.writeDefaults();
|
||||
|
||||
// Test initial
|
||||
assertArrayEquals(initialValue, modifier.read(index));
|
||||
|
||||
// Test assignment
|
||||
modifier.write(index, testValue);
|
||||
assertArrayEquals(testValue, modifier.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByteArrays() {
|
||||
// Contains a byte array we will test
|
||||
PacketContainer customPayload = new PacketContainer(Packets.Server.CUSTOM_PAYLOAD);
|
||||
StructureModifier<byte[]> bytes = customPayload.getByteArrays();
|
||||
byte[] testArray = new byte[] { 1, 2, 3 };
|
||||
|
||||
// It's NULL at first
|
||||
assertArrayEquals(null, (byte[]) bytes.read(0));
|
||||
customPayload.getModifier().writeDefaults();
|
||||
|
||||
// Then it should create an empty array
|
||||
assertArrayEquals(new byte[0], (byte[]) bytes.read(0));
|
||||
|
||||
// Check and see if we can write to it
|
||||
bytes.write(0, testArray);
|
||||
assertArrayEquals(testArray, (byte[]) bytes.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBytes() {
|
||||
PacketContainer spawnMob = new PacketContainer(Packets.Server.MOB_SPAWN);
|
||||
testPrimitive(spawnMob.getBytes(), 0, (byte)0, (byte)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShorts() {
|
||||
PacketContainer itemData = new PacketContainer(Packets.Server.ITEM_DATA);
|
||||
testPrimitive(itemData.getShorts(), 0, (short)0, (short)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntegers() {
|
||||
PacketContainer updateSign = new PacketContainer(Packets.Server.UPDATE_SIGN);
|
||||
testPrimitive(updateSign.getIntegers(), 0, (int)0, (int)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLongs() {
|
||||
PacketContainer updateTime = new PacketContainer(Packets.Server.UPDATE_TIME);
|
||||
testPrimitive(updateTime.getLongs(), 0, (long)0, (long)1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloat() {
|
||||
PacketContainer explosion = new PacketContainer(Packets.Server.EXPLOSION);
|
||||
testPrimitive(explosion.getFloat(), 0, (float)0, (float)0.8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDoubles() {
|
||||
PacketContainer explosion = new PacketContainer(Packets.Server.EXPLOSION);
|
||||
testPrimitive(explosion.getDoubles(), 0, (double)0, (double)0.8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStrings() {
|
||||
PacketContainer explosion = new PacketContainer(Packets.Server.CHAT);
|
||||
testPrimitive(explosion.getStrings(), 0, null, "hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStringArrays() {
|
||||
PacketContainer explosion = new PacketContainer(Packets.Server.UPDATE_SIGN);
|
||||
testObjectArray(explosion.getStringArrays(), 0, new String[0], new String[] { "hello", "world" });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntegerArrays() {
|
||||
// Contains a byte array we will test
|
||||
PacketContainer mapChunkBulk = new PacketContainer(Packets.Server.MAP_CHUNK_BULK);
|
||||
StructureModifier<int[]> integers = mapChunkBulk.getIntegerArrays();
|
||||
int[] testArray = new int[] { 1, 2, 3 };
|
||||
|
||||
// Pre and post conditions
|
||||
assertArrayEquals(null, (int[]) integers.read(0));
|
||||
mapChunkBulk.getModifier().writeDefaults();
|
||||
assertArrayEquals(new int[0], (int[]) integers.read(0));
|
||||
|
||||
integers.write(0, testArray);
|
||||
assertArrayEquals(testArray, (int[]) integers.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemModifier() {
|
||||
PacketContainer windowClick = new PacketContainer(Packets.Client.WINDOW_CLICK);
|
||||
|
||||
StructureModifier<ItemStack> items = windowClick.getItemModifier();
|
||||
ItemStack goldAxe = new ItemStack(Material.GOLD_AXE);
|
||||
|
||||
assertNull(items.read(0));
|
||||
|
||||
// Insert the goldaxe and check if it's there
|
||||
items.write(0, goldAxe);
|
||||
assertTrue(equivalentItem(goldAxe, items.read(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItemArrayModifier() {
|
||||
PacketContainer windowItems = new PacketContainer(Packets.Server.WINDOW_ITEMS);
|
||||
StructureModifier<ItemStack[]> itemAccess = windowItems.getItemArrayModifier();
|
||||
|
||||
ItemStack[] itemArray = new ItemStack[] {
|
||||
new ItemStack(Material.GOLD_AXE),
|
||||
new ItemStack(Material.DIAMOND_AXE)
|
||||
};
|
||||
|
||||
assertNull(itemAccess.read(0));
|
||||
|
||||
// Insert and check that it was succesful
|
||||
itemAccess.write(0, itemArray);
|
||||
|
||||
// Read back array
|
||||
ItemStack[] comparision = itemAccess.read(0);
|
||||
assertEquals(itemArray.length, comparision.length);
|
||||
|
||||
// Check that it is equivalent
|
||||
for (int i = 0; i < itemArray.length; i++) {
|
||||
assertTrue(String.format("Array element %s is not the same: %s != %s",
|
||||
i, itemArray[i], comparision[i]), equivalentItem(itemArray[i], comparision[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean equivalentItem(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
return second == null;
|
||||
} else if (second == null) {
|
||||
return false;
|
||||
} else {
|
||||
return first.getType().equals(second.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWorldTypeModifier() {
|
||||
PacketContainer loginPacket = new PacketContainer(Packets.Server.LOGIN);
|
||||
StructureModifier<WorldType> worldAccess = loginPacket.getWorldTypeModifier();
|
||||
|
||||
WorldType testValue = WorldType.LARGE_BIOMES;
|
||||
|
||||
assertNull(worldAccess.read(0));
|
||||
|
||||
// Insert and read back
|
||||
worldAccess.write(0, testValue);
|
||||
assertEquals(testValue, worldAccess.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDataWatcherModifier() {
|
||||
PacketContainer mobSpawnPacket = new PacketContainer(Packets.Server.MOB_SPAWN);
|
||||
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
|
||||
|
||||
WrappedDataWatcher dataWatcher = new WrappedDataWatcher();
|
||||
dataWatcher.setObject(1, 100);
|
||||
dataWatcher.setObject(2, 125);
|
||||
|
||||
assertNull(watcherAccessor.read(0));
|
||||
|
||||
// Insert and read back
|
||||
watcherAccessor.write(0, dataWatcher);
|
||||
assertEquals(dataWatcher, watcherAccessor.read(0));
|
||||
}
|
||||
|
||||
// Unfortunately, it might be too difficult to mock this one
|
||||
//
|
||||
// @Test
|
||||
// public void testGetEntityModifier() { }
|
||||
|
||||
// No packet expose this type directly.
|
||||
//
|
||||
// @Test
|
||||
// public void testGetPositionModifier() { }
|
||||
|
||||
@Test
|
||||
public void testGetPositionCollectionModifier() {
|
||||
PacketContainer explosionPacket = new PacketContainer(Packets.Server.EXPLOSION);
|
||||
StructureModifier<List<ChunkPosition>> positionAccessor = explosionPacket.getPositionCollectionModifier();
|
||||
|
||||
assertNull(positionAccessor.read(0));
|
||||
|
||||
List<ChunkPosition> positions = Lists.newArrayList();
|
||||
positions.add(new ChunkPosition(1, 2, 3));
|
||||
positions.add(new ChunkPosition(3, 4, 5));
|
||||
|
||||
// Insert and read back
|
||||
positionAccessor.write(0, positions);
|
||||
assertEquals(positions, positionAccessor.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWatchableCollectionModifier() {
|
||||
PacketContainer entityMetadata = new PacketContainer(Packets.Server.ENTITY_METADATA);
|
||||
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
|
||||
entityMetadata.getWatchableCollectionModifier();
|
||||
|
||||
assertNull(watchableAccessor.read(0));
|
||||
|
||||
WrappedDataWatcher watcher = new WrappedDataWatcher();
|
||||
watcher.setObject(1, 10);
|
||||
watcher.setObject(8, 10);
|
||||
|
||||
List<WrappedWatchableObject> list = watcher.getWatchableObjects();
|
||||
|
||||
// Insert and read back
|
||||
watchableAccessor.write(0, list);
|
||||
assertEquals(list, watchableAccessor.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepClone() {
|
||||
// Try constructing all the packets
|
||||
for (Integer id : Iterables.concat(
|
||||
Packets.getClientRegistry().values(),
|
||||
Packets.getServerRegistry().values() )) {
|
||||
|
||||
// Whether or not this packet has been registered
|
||||
boolean registered = Packets.Server.isSupported(id) ||
|
||||
Packets.Client.isSupported(id);
|
||||
|
||||
try {
|
||||
PacketContainer constructed = new PacketContainer(id);
|
||||
|
||||
if (!registered) {
|
||||
fail("Expected IllegalArgumentException(Packet " + id + " not registered");
|
||||
}
|
||||
|
||||
// Make sure these packets contains fields as well
|
||||
assertTrue("Constructed packet with no known fields (" + id + ")",
|
||||
constructed.getModifier().size() > 0);
|
||||
|
||||
// Initialize default values
|
||||
constructed.getModifier().writeDefaults();
|
||||
|
||||
// Clone the packet
|
||||
PacketContainer cloned = constructed.deepClone();
|
||||
|
||||
// Make sure they're equivalent
|
||||
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
|
||||
assertEquals(firstMod.size(), secondMod.size());
|
||||
|
||||
// Make sure all the fields are equivalent
|
||||
for (int i = 0; i < firstMod.size(); i++) {
|
||||
if (firstMod.getField(i).getType().isArray())
|
||||
assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i)));
|
||||
else
|
||||
testEquality(firstMod.read(i), secondMod.read(i));
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (!registered) {
|
||||
// Check the same
|
||||
assertEquals(e.getMessage(), "The packet ID " + id + " is not registered.");
|
||||
} else {
|
||||
// Something is very wrong
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to objects that support equals()
|
||||
private void testEquality(Object a, Object b) {
|
||||
if (a != null && b != null) {
|
||||
if (MinecraftReflection.isDataWatcher(a)) {
|
||||
a = watchConvert.getSpecific(a);
|
||||
b = watchConvert.getSpecific(b);
|
||||
} else if (MinecraftReflection.isItemStack(a)) {
|
||||
a = itemConvert.getSpecific(a);
|
||||
b = itemConvert.getSpecific(b);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying array as an object array.
|
||||
* @param val - array wrapped as an Object.
|
||||
* @return An object array.
|
||||
*/
|
||||
private Object[] getArray(Object val) {
|
||||
if (val instanceof Object[])
|
||||
return (Object[]) val;
|
||||
if (val == null)
|
||||
return null;
|
||||
|
||||
int arrlength = Array.getLength(val);
|
||||
Object[] outputArray = new Object[arrlength];
|
||||
|
||||
for (int i = 0; i < arrlength; ++i)
|
||||
outputArray[i] = Array.get(val, i);
|
||||
return outputArray;
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren